Skip to content

Commit b3a8c81

Browse files
committed
fix: markseen_msgs(): Mark reactions to specified messages as seen too (#7884)
This allows to remove notifications for reactions from other devices. NB: UIs should pass all messages to markseen_msgs(), incl. outgoing ones. markseen_msgs() should be called when a message comes into view or when a reaction for a message being in view arrives. Also don't emit `MsgsNoticed` from receive_imf_inner() if the chat still contains fresh hidden messages, i.e. include reactions into this logic, to avoid removing notifications for reactions until they are seen on another device.
1 parent 822a99e commit b3a8c81

5 files changed

Lines changed: 138 additions & 65 deletions

File tree

deltachat-rpc-client/tests/test_something.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -413,27 +413,32 @@ def test_dont_move_sync_msgs(acfactory, direct_imap):
413413
time.sleep(1)
414414

415415

416-
def test_reaction_seen_on_another_dev(acfactory) -> None:
416+
@pytest.mark.parametrize("chat_noticed", [False, True])
417+
def test_reaction_seen_on_another_dev(acfactory, chat_noticed) -> None:
417418
alice, bob = acfactory.get_online_accounts(2)
418419
alice2 = alice.clone()
419420
alice2.start_io()
420421

421422
alice_contact_bob = alice.create_contact(bob, "Bob")
422423
alice_chat_bob = alice_contact_bob.create_chat()
423-
alice_chat_bob.send_text("Hello!")
424+
alice_msg = alice_chat_bob.send_text("Hello!")
424425

425426
event = bob.wait_for_incoming_msg_event()
426427
msg_id = event.msg_id
427428

428-
message = bob.get_message_by_id(msg_id)
429-
snapshot = message.get_snapshot()
429+
bob_msg = bob.get_message_by_id(msg_id)
430+
snapshot = bob_msg.get_snapshot()
430431
snapshot.chat.accept()
431-
message.send_reaction("😎")
432+
bob_msg.send_reaction("😎")
432433
for a in [alice, alice2]:
433434
a.wait_for_event(EventType.INCOMING_REACTION)
434435

435436
alice2.clear_all_events()
436-
alice_chat_bob.mark_noticed()
437+
if chat_noticed:
438+
alice_chat_bob.mark_noticed()
439+
else:
440+
# UIs are supposed to mark messages being in view as seen, not reactions themselves.
441+
alice_msg.mark_seen()
437442
chat_id = alice2.wait_for_event(EventType.MSGS_NOTICED).chat_id
438443
alice2_chat_bob = alice2.create_chat(bob)
439444
assert chat_id == alice2_chat_bob.id

src/message.rs

Lines changed: 61 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,7 +1829,7 @@ pub async fn delete_msgs_ex(
18291829
Ok(())
18301830
}
18311831

1832-
/// Marks requested messages as seen.
1832+
/// Marks requested messages and reactions to them as seen.
18331833
pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()> {
18341834
if msg_ids.is_empty() {
18351835
return Ok(());
@@ -1843,10 +1843,18 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
18431843

18441844
let mut msgs = Vec::with_capacity(msg_ids.len());
18451845
for &id in &msg_ids {
1846-
if let Some(msg) = context
1846+
let Some(rfc724_mid): Option<String> = context
18471847
.sql
1848-
.query_row_optional(
1848+
.query_get_value("SELECT rfc724_mid FROM msgs WHERE id=?", (id,))
1849+
.await?
1850+
else {
1851+
continue;
1852+
};
1853+
context
1854+
.sql
1855+
.query_map(
18491856
"SELECT
1857+
m.id AS id,
18501858
m.chat_id AS chat_id,
18511859
m.state AS state,
18521860
m.ephemeral_timer AS ephemeral_timer,
@@ -1857,9 +1865,11 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
18571865
c.archived AS archived,
18581866
c.blocked AS blocked
18591867
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
1860-
WHERE m.id=? AND m.chat_id>9",
1861-
(id,),
1868+
WHERE (m.id=? OR m.mime_in_reply_to=? AND m.hidden=1)
1869+
AND m.chat_id>9 AND ?<=m.state AND m.state<?",
1870+
(id, rfc724_mid, MessageState::InFresh, MessageState::InSeen),
18621871
|row| {
1872+
let id: MsgId = row.get("id")?;
18631873
let chat_id: ChatId = row.get("chat_id")?;
18641874
let state: MessageState = row.get("state")?;
18651875
let param: Params = row.get::<_, String>("param")?.parse().unwrap_or_default();
@@ -1884,11 +1894,14 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
18841894
ephemeral_timer,
18851895
))
18861896
},
1897+
|rows| {
1898+
for row in rows {
1899+
msgs.push(row?);
1900+
}
1901+
Ok(())
1902+
},
18871903
)
1888-
.await?
1889-
{
1890-
msgs.push(msg);
1891-
}
1904+
.await?;
18921905
}
18931906

18941907
if msgs
@@ -1917,48 +1930,45 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
19171930
_curr_ephemeral_timer,
19181931
) in msgs
19191932
{
1920-
if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
1921-
update_msg_state(context, id, MessageState::InSeen).await?;
1922-
info!(context, "Seen message {}.", id);
1923-
1924-
markseen_on_imap_table(context, &curr_rfc724_mid).await?;
1925-
1926-
// Read receipts for system messages are never sent to contacts.
1927-
// These messages have no place to display received read receipt
1928-
// anyway. And since their text is locally generated,
1929-
// quoting them is dangerous as it may contain contact names. E.g., for original message
1930-
// "Group left by me", a read receipt will quote "Group left by <name>", and the name can
1931-
// be a display name stored in address book rather than the name sent in the From field by
1932-
// the user.
1933-
//
1934-
// We also don't send read receipts for contact requests.
1935-
// Read receipts will not be sent even after accepting the chat.
1936-
let to_id = if curr_blocked == Blocked::Not
1937-
&& !curr_hidden
1938-
&& curr_param.get_bool(Param::WantsMdn).unwrap_or_default()
1939-
&& curr_param.get_cmd() == SystemMessage::Unknown
1940-
&& context.should_send_mdns().await?
1941-
{
1942-
Some(curr_from_id)
1943-
} else if context.get_config_bool(Config::BccSelf).await? {
1944-
Some(ContactId::SELF)
1945-
} else {
1946-
None
1947-
};
1948-
if let Some(to_id) = to_id {
1949-
context
1950-
.sql
1951-
.execute(
1952-
"INSERT INTO smtp_mdns (msg_id, from_id, rfc724_mid) VALUES(?, ?, ?)",
1953-
(id, to_id, curr_rfc724_mid),
1954-
)
1955-
.await
1956-
.context("failed to insert into smtp_mdns")?;
1957-
context.scheduler.interrupt_smtp().await;
1958-
}
1959-
if !curr_hidden {
1960-
updated_chat_ids.insert(curr_chat_id);
1961-
}
1933+
update_msg_state(context, id, MessageState::InSeen).await?;
1934+
info!(context, "Seen message {}.", id);
1935+
1936+
markseen_on_imap_table(context, &curr_rfc724_mid).await?;
1937+
1938+
// Read receipts for system messages are never sent to contacts. These messages have no
1939+
// place to display received read receipt anyway. And since their text is locally generated,
1940+
// quoting them is dangerous as it may contain contact names. E.g., for original message
1941+
// "Group left by me", a read receipt will quote "Group left by <name>", and the name can be
1942+
// a display name stored in address book rather than the name sent in the From field by the
1943+
// user.
1944+
//
1945+
// We also don't send read receipts for contact requests. Read receipts will not be sent
1946+
// even after accepting the chat.
1947+
let to_id = if curr_blocked == Blocked::Not
1948+
&& !curr_hidden
1949+
&& curr_param.get_bool(Param::WantsMdn).unwrap_or_default()
1950+
&& curr_param.get_cmd() == SystemMessage::Unknown
1951+
&& context.should_send_mdns().await?
1952+
{
1953+
Some(curr_from_id)
1954+
} else if context.get_config_bool(Config::BccSelf).await? {
1955+
Some(ContactId::SELF)
1956+
} else {
1957+
None
1958+
};
1959+
if let Some(to_id) = to_id {
1960+
context
1961+
.sql
1962+
.execute(
1963+
"INSERT INTO smtp_mdns (msg_id, from_id, rfc724_mid) VALUES(?, ?, ?)",
1964+
(id, to_id, curr_rfc724_mid),
1965+
)
1966+
.await
1967+
.context("failed to insert into smtp_mdns")?;
1968+
context.scheduler.interrupt_smtp().await;
1969+
}
1970+
if !curr_hidden {
1971+
updated_chat_ids.insert(curr_chat_id);
19621972
}
19631973
archived_chats_maybe_noticed |= curr_state == MessageState::InFresh
19641974
&& !curr_hidden

src/reaction.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,4 +1108,37 @@ Content-Transfer-Encoding: 7bit\r
11081108

11091109
Ok(())
11101110
}
1111+
1112+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1113+
async fn test_markseen_referenced_msg() -> Result<()> {
1114+
let mut tcm = TestContextManager::new();
1115+
let alice = &tcm.alice().await;
1116+
let bob = &tcm.bob().await;
1117+
let chat_id = alice.create_chat(bob).await.id;
1118+
1119+
let alice_msg_id = send_text_msg(alice, chat_id, "foo".to_string()).await?;
1120+
let sent_msg = alice.pop_sent_msg().await;
1121+
let bob_msg = bob.recv_msg(&sent_msg).await;
1122+
bob_msg.chat_id.accept(bob).await?;
1123+
1124+
send_reaction(bob, bob_msg.id, "👀").await?;
1125+
let sent_msg = bob.pop_sent_msg().await;
1126+
let alice_reaction = alice.recv_msg_hidden(&sent_msg).await;
1127+
assert_eq!(alice_reaction.state, MessageState::InFresh);
1128+
1129+
markseen_msgs(alice, vec![alice_msg_id]).await?;
1130+
let alice_reaction = Message::load_from_db(alice, alice_reaction.id).await?;
1131+
assert_eq!(alice_reaction.state, MessageState::InSeen);
1132+
assert_eq!(
1133+
alice
1134+
.sql
1135+
.count(
1136+
"SELECT COUNT(*) FROM smtp_mdns WHERE from_id=?",
1137+
(ContactId::SELF,)
1138+
)
1139+
.await?,
1140+
1
1141+
);
1142+
Ok(())
1143+
}
11111144
}

src/receive_imf.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ use crate::message::{
3535
rfc724_mid_exists,
3636
};
3737
use crate::mimeparser::{
38-
AvatarAction, GossipedKey, MimeMessage, PreMessageMode, SystemMessage, parse_message_ids,
38+
AvatarAction, GossipedKey, MimeMessage, PreMessageMode, SystemMessage, parse_message_id,
39+
parse_message_ids,
3940
};
4041
use crate::param::{Param, Params};
4142
use crate::peer_channels::{add_gossip_peer_from_header, insert_topic_stub, iroh_topic_from_str};
@@ -1023,7 +1024,7 @@ UPDATE config SET value=? WHERE keyname='configured_addr' AND value!=?1
10231024
"
10241025
UPDATE msgs SET state=? WHERE
10251026
state=? AND
1026-
hidden=0 AND
1027+
(hidden=0 OR hidden=1) AND
10271028
chat_id=? AND
10281029
timestamp<?",
10291030
(
@@ -1035,7 +1036,18 @@ UPDATE msgs SET state=? WHERE
10351036
)
10361037
.await
10371038
.context("UPDATE msgs.state")?;
1038-
if chat_id.get_fresh_msg_cnt(context).await? == 0 {
1039+
let n_fresh_msgs = context
1040+
.sql
1041+
.count(
1042+
"
1043+
SELECT COUNT(*) FROM msgs WHERE
1044+
state=? AND
1045+
(hidden=0 OR hidden=1) AND
1046+
chat_id=?",
1047+
(MessageState::InFresh, chat_id),
1048+
)
1049+
.await?;
1050+
if n_fresh_msgs == 0 {
10391051
// Removes all notifications for the chat in UIs.
10401052
context.emit_event(EventType::MsgsNoticed(chat_id));
10411053
} else {
@@ -1993,9 +2005,13 @@ async fn add_parts(
19932005
)
19942006
.await?;
19952007

1996-
let mime_in_reply_to = mime_parser
1997-
.get_header(HeaderDef::InReplyTo)
1998-
.unwrap_or_default();
2008+
let mime_in_reply_to = match mime_parser.get_header(HeaderDef::InReplyTo) {
2009+
Some(in_reply_to) => parse_message_id(in_reply_to)
2010+
.log_err(context)
2011+
.ok()
2012+
.unwrap_or_default(),
2013+
None => "".to_string(),
2014+
};
19992015
let mime_references = mime_parser
20002016
.get_header(HeaderDef::References)
20012017
.unwrap_or_default();
@@ -2122,7 +2138,7 @@ async fn add_parts(
21222138
let is_incoming_fresh = mime_parser.incoming && !seen;
21232139
set_msg_reaction(
21242140
context,
2125-
mime_in_reply_to,
2141+
&mime_in_reply_to,
21262142
chat_id,
21272143
from_id,
21282144
sort_timestamp,
@@ -2264,7 +2280,7 @@ RETURNING id
22642280
} else {
22652281
Vec::new()
22662282
},
2267-
if trash { "" } else { mime_in_reply_to },
2283+
if trash { "" } else { &mime_in_reply_to },
22682284
if trash { "" } else { mime_references },
22692285
!trash && save_mime_modified,
22702286
if trash { "" } else { part.error.as_deref().unwrap_or_default() },

src/sql/migrations.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2343,6 +2343,15 @@ ALTER TABLE contacts ADD COLUMN name_normalized TEXT;
23432343
.await?;
23442344
}
23452345

2346+
inc_and_check(&mut migration_version, 149)?;
2347+
if dbversion < migration_version {
2348+
sql.execute_migration(
2349+
"CREATE INDEX msgs_index10 ON msgs (mime_in_reply_to)",
2350+
migration_version,
2351+
)
2352+
.await?;
2353+
}
2354+
23462355
let new_version = sql
23472356
.get_raw_config_int(VERSION_CFG)
23482357
.await?

0 commit comments

Comments
 (0)