Skip to content

Commit fe0c995

Browse files
committed
feat: Assign message to ad-hoc group with matching name and members (#5385)
This should fix ad-hoc groups splitting when messages are fetched out of order from different folders or otherwise reordered, or some messages are missing so that the messages reference chain is broken, or a member was removed from the thread and readded later, etc. Even if this way two different threads are merged, it looks acceptable, having many threads with the same name/subject and members isn't a common use case.
1 parent c469fcb commit fe0c995

File tree

3 files changed

+117
-4
lines changed

3 files changed

+117
-4
lines changed

src/receive_imf.rs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use crate::peerstate::Peerstate;
3535
use crate::reaction::{set_msg_reaction, Reaction};
3636
use crate::securejoin::{self, handle_securejoin_handshake, observe_securejoin_on_other_device};
3737
use crate::simplify;
38-
use crate::sql;
38+
use crate::sql::{self, params_iter};
3939
use crate::stock_str;
4040
use crate::sync::Sync::*;
4141
use crate::tools::{self, buf_compress, remove_subject_prefix};
@@ -1829,9 +1829,6 @@ async fn lookup_chat_or_create_adhoc_group(
18291829
{
18301830
return Ok(Some((new_chat_id, new_chat_id_blocked)));
18311831
}
1832-
if !allow_creation {
1833-
return Ok(None);
1834-
}
18351832
// Partial download may be an encrypted message with protected Subject header. We do not want to
18361833
// create a group with "..." or "Encrypted message" as a subject. The same is for undecipherable
18371834
// messages. Instead, assign the message to 1:1 chat with the sender.
@@ -1854,6 +1851,48 @@ async fn lookup_chat_or_create_adhoc_group(
18541851
.get_subject()
18551852
.map(|s| remove_subject_prefix(&s))
18561853
.unwrap_or_else(|| "👥📧".to_string());
1854+
let mut contact_ids = Vec::with_capacity(to_ids.len() + 1);
1855+
contact_ids.extend(to_ids);
1856+
if !contact_ids.contains(&from_id) {
1857+
contact_ids.push(from_id);
1858+
}
1859+
if let Some((chat_id, blocked)) = context
1860+
.sql
1861+
.query_row_optional(
1862+
&format!(
1863+
"SELECT c.id, c.blocked
1864+
FROM chats c INNER JOIN msgs m ON c.id=m.chat_id
1865+
WHERE m.hidden=0 AND c.grpid='' AND c.name=?
1866+
AND (SELECT COUNT(*) FROM chats_contacts
1867+
WHERE chat_id=c.id)=?
1868+
AND (SELECT COUNT(*) FROM chats_contacts
1869+
WHERE chat_id=c.id
1870+
AND contact_id NOT IN ({}))=0
1871+
ORDER BY m.timestamp DESC",
1872+
sql::repeat_vars(contact_ids.len()),
1873+
),
1874+
rusqlite::params_from_iter(
1875+
params_iter(&[&grpname])
1876+
.chain(params_iter(&[contact_ids.len()]))
1877+
.chain(params_iter(&contact_ids)),
1878+
),
1879+
|row| {
1880+
let id: ChatId = row.get(0)?;
1881+
let blocked: Blocked = row.get(1)?;
1882+
Ok((id, blocked))
1883+
},
1884+
)
1885+
.await?
1886+
{
1887+
info!(
1888+
context,
1889+
"Assigning message to ad-hoc group {chat_id} with matching name and members."
1890+
);
1891+
return Ok(Some((chat_id, blocked)));
1892+
}
1893+
if !allow_creation {
1894+
return Ok(None);
1895+
}
18571896
create_adhoc_group(
18581897
context,
18591898
mime_parser,

src/receive_imf/tests.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,71 @@ async fn test_adhoc_group_show_all() {
204204
assert_eq!(chat::get_chat_contacts(&t, chat_id).await.unwrap().len(), 3);
205205
}
206206

207+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
208+
async fn test_adhoc_groups_merge() -> Result<()> {
209+
let mut tcm = TestContextManager::new();
210+
let alice = &tcm.alice().await;
211+
receive_imf(
212+
alice,
213+
b"From: bob@example.net\n\
214+
To: alice@example.org, claire@example.com\n\
215+
Message-ID: <1111@example.net>\n\
216+
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
217+
Subject: New thread\n\
218+
\n\
219+
The first of us should create a thread as discussed\n",
220+
false,
221+
)
222+
.await?;
223+
receive_imf(
224+
alice,
225+
b"From: alice@example.org\n\
226+
To: bob@example.net, claire@example.com\n\
227+
Message-ID: <2222@example.org>\n\
228+
Date: Sun, 22 Mar 2020 22:37:58 +0000\n\
229+
Subject: New thread\n\
230+
\n\
231+
The first of us should create a thread as discussed\n",
232+
false,
233+
)
234+
.await?;
235+
let chats = Chatlist::try_load(alice, 0, None, None).await?;
236+
assert_eq!(chats.len(), 1);
237+
let chat_id = chats.get_chat_id(0)?;
238+
assert_eq!(chat_id.get_msg_cnt(alice).await?, 2);
239+
240+
// If member list doesn't match, threads aren't merged.
241+
receive_imf(
242+
alice,
243+
b"From: bob@example.net\n\
244+
To: alice@example.org, claire@example.com, fiona@example.net\n\
245+
Message-ID: <3333@example.net>\n\
246+
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
247+
Subject: New thread\n\
248+
\n\
249+
This is another thread, with Fiona\n",
250+
false,
251+
)
252+
.await?;
253+
let chats = Chatlist::try_load(alice, 0, None, None).await?;
254+
assert_eq!(chats.len(), 2);
255+
receive_imf(
256+
alice,
257+
b"From: bob@example.net\n\
258+
To: alice@example.org, fiona@example.net\n\
259+
Message-ID: <4444@example.net>\n\
260+
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
261+
Subject: New thread\n\
262+
\n\
263+
This is yet another thread, with Fiona and 0 Claires\n",
264+
false,
265+
)
266+
.await?;
267+
let chats = Chatlist::try_load(alice, 0, None, None).await?;
268+
assert_eq!(chats.len(), 3);
269+
Ok(())
270+
}
271+
207272
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
208273
async fn test_read_receipt_and_unarchive() -> Result<()> {
209274
// create alice's account

src/sql/migrations.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,15 @@ CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
10281028
.await?;
10291029
}
10301030

1031+
inc_and_check(&mut migration_version, 121)?;
1032+
if dbversion < migration_version {
1033+
sql.execute_migration(
1034+
"CREATE INDEX chats_index4 ON chats (name)",
1035+
migration_version,
1036+
)
1037+
.await?;
1038+
}
1039+
10311040
let new_version = sql
10321041
.get_raw_config_int(VERSION_CFG)
10331042
.await?

0 commit comments

Comments
 (0)