Skip to content

Commit aaa0b58

Browse files
committed
feat: Mark reactions as IMAP-seen in marknoticed_chat() (#6210)
When a reaction notification is shown in the UIs, there's an option "Mark Read", but the UIs are unaware of reactions message ids, moreover there are no `msgs` rows for incoming reactions in the core, so the UIs just call `marknoticed_chat()` in this case. We don't want to introduce reactions message ids to the UIs (at least currently), but let's make received reactions usual messages, just hidden and `InFresh`, so that the existing `\Seen` flag synchronisation mechanism works for them, and mark the last fresh hidden incoming message in the chat as seen in `marknoticed_chat()` to trigger emitting `MsgsNoticed` on other devices. There's a problem though that another device may have more reactions received and not yet seen notifications are removed from it when handling `MsgsNoticed`, but the same problem already exists for "usual" messages, so let's not solve it for now. It's interesting that sent out reactions are already hidden messages, so this change mostly just unifies things.
1 parent 6be96d3 commit aaa0b58

File tree

6 files changed

+118
-39
lines changed

6 files changed

+118
-39
lines changed

deltachat-rpc-client/src/deltachat_rpc_client/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class EventType(str, Enum):
3939
ERROR_SELF_NOT_IN_GROUP = "ErrorSelfNotInGroup"
4040
MSGS_CHANGED = "MsgsChanged"
4141
REACTIONS_CHANGED = "ReactionsChanged"
42+
INCOMING_REACTION = "IncomingReaction"
4243
INCOMING_MSG = "IncomingMsg"
4344
INCOMING_MSG_BUNCH = "IncomingMsgBunch"
4445
MSGS_NOTICED = "MsgsNoticed"

deltachat-rpc-client/tests/test_something.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,43 @@ def test_message(acfactory) -> None:
285285
assert reactions == snapshot.reactions
286286

287287

288+
def test_reaction_seen_on_another_dev(acfactory, tmp_path) -> None:
289+
alice, bob = acfactory.get_online_accounts(2)
290+
alice.export_backup(tmp_path)
291+
files = list(tmp_path.glob("*.tar"))
292+
alice2 = acfactory.get_unconfigured_account()
293+
alice2.import_backup(files[0])
294+
alice2.start_io()
295+
296+
bob_addr = bob.get_config("addr")
297+
alice_contact_bob = alice.create_contact(bob_addr, "Bob")
298+
alice_chat_bob = alice_contact_bob.create_chat()
299+
alice_chat_bob.send_text("Hello!")
300+
301+
event = bob.wait_for_incoming_msg_event()
302+
msg_id = event.msg_id
303+
304+
message = bob.get_message_by_id(msg_id)
305+
snapshot = message.get_snapshot()
306+
snapshot.chat.accept()
307+
message.send_reaction("😎")
308+
for a in [alice, alice2]:
309+
while True:
310+
event = a.wait_for_event()
311+
if event.kind == EventType.INCOMING_REACTION:
312+
break
313+
314+
alice_chat_bob.mark_noticed()
315+
while True:
316+
event = alice2.wait_for_event()
317+
if event.kind == EventType.MSGS_NOTICED:
318+
chat_id = event.chat_id
319+
break
320+
alice2_contact_bob = alice2.get_contact_by_addr(bob_addr)
321+
alice2_chat_bob = alice2_contact_bob.create_chat()
322+
assert chat_id == alice2_chat_bob.id
323+
324+
288325
def test_is_bot(acfactory) -> None:
289326
"""Test that we can recognize messages submitted by bots."""
290327
alice, bob = acfactory.get_online_accounts(2)

src/chat.rs

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3290,10 +3290,10 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()>
32903290
.query_map(
32913291
"SELECT DISTINCT(m.chat_id) FROM msgs m
32923292
LEFT JOIN chats c ON m.chat_id=c.id
3293-
WHERE m.state=10 AND m.hidden=0 AND m.chat_id>9 AND c.blocked=0 AND c.archived=1",
3294-
(),
3293+
WHERE m.state=10 AND m.chat_id>9 AND c.blocked=0 AND c.archived=1",
3294+
(),
32953295
|row| row.get::<_, ChatId>(0),
3296-
|ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into)
3296+
|ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
32973297
)
32983298
.await?;
32993299
if chat_ids_in_archive.is_empty() {
@@ -3304,7 +3304,7 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()>
33043304
.sql
33053305
.execute(
33063306
&format!(
3307-
"UPDATE msgs SET state=13 WHERE state=10 AND hidden=0 AND chat_id IN ({});",
3307+
"UPDATE msgs SET state=13 WHERE state=10 AND chat_id IN ({});",
33083308
sql::repeat_vars(chat_ids_in_archive.len())
33093309
),
33103310
rusqlite::params_from_iter(&chat_ids_in_archive),
@@ -3314,20 +3314,39 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()>
33143314
context.emit_event(EventType::MsgsNoticed(chat_id_in_archive));
33153315
chatlist_events::emit_chatlist_item_changed(context, chat_id_in_archive);
33163316
}
3317-
} else if context
3318-
.sql
3319-
.execute(
3320-
"UPDATE msgs
3321-
SET state=?
3322-
WHERE state=?
3323-
AND hidden=0
3324-
AND chat_id=?;",
3325-
(MessageState::InNoticed, MessageState::InFresh, chat_id),
3326-
)
3327-
.await?
3328-
== 0
3329-
{
3330-
return Ok(());
3317+
} else {
3318+
let conn_fn = |conn: &mut rusqlite::Connection| {
3319+
let nr_msgs_noticed = conn.execute(
3320+
"UPDATE msgs
3321+
SET state=?
3322+
WHERE state=?
3323+
AND hidden=0
3324+
AND chat_id=?",
3325+
(MessageState::InNoticed, MessageState::InFresh, chat_id),
3326+
)?;
3327+
let mut stmt = conn.prepare(
3328+
"SELECT id FROM msgs
3329+
WHERE state>=? AND state<?
3330+
AND hidden=1
3331+
AND chat_id=?
3332+
ORDER BY id DESC LIMIT 100",
3333+
)?;
3334+
let hidden_msgs = stmt
3335+
.query_map(
3336+
(MessageState::InFresh, MessageState::InSeen, chat_id),
3337+
|row| {
3338+
let id: MsgId = row.get(0)?;
3339+
Ok(id)
3340+
},
3341+
)?
3342+
.collect::<std::result::Result<Vec<_>, _>>()?;
3343+
Ok((nr_msgs_noticed, hidden_msgs))
3344+
};
3345+
let (nr_msgs_noticed, hidden_msgs) = context.sql.call_write(conn_fn).await?;
3346+
if nr_msgs_noticed == 0 && hidden_msgs.is_empty() {
3347+
return Ok(());
3348+
}
3349+
message::markseen_msgs(context, hidden_msgs).await?;
33313350
}
33323351

33333352
context.emit_event(EventType::MsgsNoticed(chat_id));
@@ -3372,7 +3391,6 @@ pub(crate) async fn mark_old_messages_as_noticed(
33723391
"UPDATE msgs
33733392
SET state=?
33743393
WHERE state=?
3375-
AND hidden=0
33763394
AND chat_id=?
33773395
AND timestamp<=?;",
33783396
(

src/reaction.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ mod tests {
397397
use deltachat_contact_tools::ContactAddress;
398398

399399
use super::*;
400-
use crate::chat::{forward_msgs, get_chat_msgs, send_text_msg};
400+
use crate::chat::{forward_msgs, get_chat_msgs, marknoticed_chat, send_text_msg};
401401
use crate::chatlist::Chatlist;
402402
use crate::config::Config;
403403
use crate::contact::{Contact, Origin};
@@ -663,7 +663,8 @@ Here's my footer -- bob@example.net"
663663
assert_eq!(get_chat_msgs(&bob, bob_msg.chat_id).await?.len(), 2);
664664

665665
let bob_reaction_msg = bob.pop_sent_msg().await;
666-
alice.recv_msg_trash(&bob_reaction_msg).await;
666+
let alice_reaction_msg = alice.recv_msg_hidden(&bob_reaction_msg).await;
667+
assert_eq!(alice_reaction_msg.state, MessageState::InFresh);
667668
assert_eq!(get_chat_msgs(&alice, chat_alice.id).await?.len(), 2);
668669

669670
let reactions = get_msg_reactions(&alice, alice_msg.sender_msg_id).await?;
@@ -680,6 +681,20 @@ Here's my footer -- bob@example.net"
680681
expect_incoming_reactions_event(&alice, alice_msg.sender_msg_id, *bob_id, "👍").await?;
681682
expect_no_unwanted_events(&alice).await;
682683

684+
marknoticed_chat(&alice, chat_alice.id).await?;
685+
assert_eq!(
686+
alice_reaction_msg.id.get_state(&alice).await?,
687+
MessageState::InSeen
688+
);
689+
// Reactions don't request MDNs.
690+
assert_eq!(
691+
alice
692+
.sql
693+
.count("SELECT COUNT(*) FROM smtp_mdns", ())
694+
.await?,
695+
0
696+
);
697+
683698
// Alice reacts to own message.
684699
send_reaction(&alice, alice_msg.sender_msg_id, "👍 😀")
685700
.await
@@ -719,7 +734,7 @@ Here's my footer -- bob@example.net"
719734
bob_msg1.chat_id.accept(&bob).await?;
720735
send_reaction(&bob, bob_msg1.id, "👍").await?;
721736
let bob_send_reaction = bob.pop_sent_msg().await;
722-
alice.recv_msg_trash(&bob_send_reaction).await;
737+
alice.recv_msg_hidden(&bob_send_reaction).await;
723738
expect_incoming_reactions_event(&alice, alice_msg1.sender_msg_id, alice_bob_id, "👍")
724739
.await?;
725740
expect_no_unwanted_events(&alice).await;
@@ -882,7 +897,7 @@ Here's my footer -- bob@example.net"
882897
let bob_reaction_msg = bob.pop_sent_msg().await;
883898

884899
// Alice receives a reaction.
885-
alice.recv_msg_trash(&bob_reaction_msg).await;
900+
alice.recv_msg_hidden(&bob_reaction_msg).await;
886901

887902
let reactions = get_msg_reactions(&alice, alice_msg_id).await?;
888903
assert_eq!(reactions.to_string(), "👍1");
@@ -934,7 +949,7 @@ Here's my footer -- bob@example.net"
934949
{
935950
send_reaction(&alice2, alice2_msg.id, "👍").await?;
936951
let msg = alice2.pop_sent_msg().await;
937-
alice1.recv_msg_trash(&msg).await;
952+
alice1.recv_msg_hidden(&msg).await;
938953
}
939954

940955
// Check that the status is still the same.
@@ -956,7 +971,7 @@ Here's my footer -- bob@example.net"
956971
let alice1_msg = alice1.recv_msg(&alice0.pop_sent_msg().await).await;
957972

958973
send_reaction(&alice0, alice0_msg_id, "👀").await?;
959-
alice1.recv_msg_trash(&alice0.pop_sent_msg().await).await;
974+
alice1.recv_msg_hidden(&alice0.pop_sent_msg().await).await;
960975

961976
expect_reactions_changed_event(&alice0, chat_id, alice0_msg_id, ContactId::SELF).await?;
962977
expect_reactions_changed_event(&alice1, alice1_msg.chat_id, alice1_msg.id, ContactId::SELF)

src/receive_imf.rs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,7 @@ async fn add_parts(
760760
// (of course, the user can add other chats manually later)
761761
let to_id: ContactId;
762762
let state: MessageState;
763-
let mut hidden = false;
763+
let mut hidden = is_reaction;
764764
let mut needs_delete_job = false;
765765
let mut restore_protection = false;
766766

@@ -1018,12 +1018,7 @@ async fn add_parts(
10181018
}
10191019
}
10201020

1021-
state = if seen
1022-
|| fetching_existing_messages
1023-
|| is_mdn
1024-
|| is_reaction
1025-
|| chat_id_blocked == Blocked::Yes
1026-
{
1021+
state = if seen || fetching_existing_messages || is_mdn || chat_id_blocked == Blocked::Yes {
10271022
MessageState::InSeen
10281023
} else {
10291024
MessageState::InFresh
@@ -1232,7 +1227,7 @@ async fn add_parts(
12321227
}
12331228

12341229
let orig_chat_id = chat_id;
1235-
let mut chat_id = if is_mdn || is_reaction {
1230+
let mut chat_id = if is_mdn {
12361231
DC_CHAT_ID_TRASH
12371232
} else {
12381233
chat_id.unwrap_or_else(|| {
@@ -1407,7 +1402,7 @@ async fn add_parts(
14071402
// that the ui should show button to display the full message.
14081403

14091404
// a flag used to avoid adding "show full message" button to multiple parts of the message.
1410-
let mut save_mime_modified = mime_parser.is_mime_modified;
1405+
let mut save_mime_modified = mime_parser.is_mime_modified && !hidden;
14111406

14121407
let mime_headers = if save_mime_headers || save_mime_modified {
14131408
let headers = if !mime_parser.decoded_data.is_empty() {
@@ -1599,10 +1594,10 @@ RETURNING id
15991594
state,
16001595
is_dc_message,
16011596
if trash { "" } else { msg },
1602-
if trash { None } else { message::normalize_text(msg) },
1603-
if trash { "" } else { &subject },
1597+
if trash || hidden { None } else { message::normalize_text(msg) },
1598+
if trash || hidden { "" } else { &subject },
16041599
// txt_raw might contain invalid utf8
1605-
if trash { "" } else { &txt_raw },
1600+
if trash || hidden { "" } else { &txt_raw },
16061601
if trash {
16071602
"".to_string()
16081603
} else {
@@ -1618,7 +1613,7 @@ RETURNING id
16181613
mime_in_reply_to,
16191614
mime_references,
16201615
mime_modified,
1621-
part.error.as_deref().unwrap_or_default(),
1616+
if trash || hidden { "" } else { part.error.as_deref().unwrap_or_default() },
16221617
ephemeral_timer,
16231618
ephemeral_timestamp,
16241619
if is_partial_download.is_some() {
@@ -1628,7 +1623,7 @@ RETURNING id
16281623
} else {
16291624
DownloadState::Done
16301625
},
1631-
mime_parser.hop_info
1626+
if trash || hidden { "" } else { &mime_parser.hop_info },
16321627
],
16331628
|row| {
16341629
let msg_id: MsgId = row.get(0)?;

src/test_utils.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,19 @@ impl TestContext {
606606
msg
607607
}
608608

609+
/// Receive a message using the `receive_imf()` pipeline. Panics if it's not hidden.
610+
pub async fn recv_msg_hidden(&self, msg: &SentMessage<'_>) -> Message {
611+
let received = self
612+
.recv_msg_opt(msg)
613+
.await
614+
.expect("receive_imf() seems not to have added a new message to the db");
615+
let msg = Message::load_from_db(self, *received.msg_ids.last().unwrap())
616+
.await
617+
.unwrap();
618+
assert!(msg.hidden);
619+
msg
620+
}
621+
609622
/// Receive a message using the `receive_imf()` pipeline. This is similar
610623
/// to `recv_msg()`, but doesn't assume that the message is shown in the chat.
611624
pub async fn recv_msg_opt(

0 commit comments

Comments
 (0)