Skip to content

Commit 3214eb6

Browse files
committed
feat: Sort received outgoing message down if it's fresher than all non fresh messages
Received messages shouldn't mingle with just sent ones and appear somewhere in the middle of the chat, so we go after the newest non fresh message. But if a received outgoing message is older than some `InSeen` message, better sort the received message purely by timestamp (this is an heuristic in order not to break the Gmail-like case simulated by `verified_chats::test_old_message_4()`). We could place the received message just before that `InSeen` message, but anyway the user may not notice it. At least this fixes outgoing messages sorting for shared accounts where messages from other devices should be sorted the same way as incoming ones.
1 parent f1ca689 commit 3214eb6

File tree

5 files changed

+68
-14
lines changed

5 files changed

+68
-14
lines changed

src/chat.rs

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -616,8 +616,9 @@ impl ChatId {
616616
contact_id: Option<ContactId>,
617617
) -> Result<()> {
618618
let sort_to_bottom = true;
619+
let (received, incoming) = (false, false);
619620
let ts = self
620-
.calc_sort_timestamp(context, timestamp_sent, sort_to_bottom, false)
621+
.calc_sort_timestamp(context, timestamp_sent, sort_to_bottom, received, incoming)
621622
.await?
622623
// Always sort protection messages below `SystemMessage::SecurejoinWait{,Timeout}` ones
623624
// in case of race conditions.
@@ -1392,12 +1393,14 @@ impl ChatId {
13921393
/// corresponding event in case of a system message (usually the current system time).
13931394
/// `always_sort_to_bottom` makes this ajust the returned timestamp up so that the message goes
13941395
/// to the chat bottom.
1396+
/// `received` -- whether the message is received. Otherwise being sent.
13951397
/// `incoming` -- whether the message is incoming.
13961398
pub(crate) async fn calc_sort_timestamp(
13971399
self,
13981400
context: &Context,
13991401
message_timestamp: i64,
14001402
always_sort_to_bottom: bool,
1403+
received: bool,
14011404
incoming: bool,
14021405
) -> Result<i64> {
14031406
let mut sort_timestamp = cmp::min(message_timestamp, smeared_time(context));
@@ -1418,25 +1421,38 @@ impl ChatId {
14181421
(self, MessageState::OutDraft),
14191422
)
14201423
.await?
1421-
} else if incoming {
1422-
// get newest non fresh message for this chat.
1423-
1424-
// If a user hasn't been online for some time, the Inbox is fetched first and then the
1425-
// Sentbox. In order for Inbox and Sent messages to be allowed to mingle, outgoing
1426-
// messages are purely sorted by their sent timestamp. NB: The Inbox must be fetched
1427-
// first otherwise Inbox messages would be always below old Sentbox messages. We could
1428-
// take in the query below only incoming messages, but then new incoming messages would
1429-
// mingle with just sent outgoing ones and apear somewhere in the middle of the chat.
1424+
} else if received {
1425+
// Received messages shouldn't mingle with just sent ones and appear somewhere in the
1426+
// middle of the chat, so we go after the newest non fresh message.
1427+
//
1428+
// But if a received outgoing message is older than some seen message, better sort the
1429+
// received message purely by timestamp. We could place it just before that seen
1430+
// message, but anyway the user may not notice it.
1431+
//
1432+
// NB: Received outgoing messages may break sorting of fresh incoming ones, but this
1433+
// shouldn't happen frequently. Seen incoming messages don't really break sorting of
1434+
// fresh ones, they rather mean that older incoming messages are actually seen as well.
14301435
context
14311436
.sql
1432-
.query_get_value(
1433-
"SELECT MAX(timestamp)
1437+
.query_row_optional(
1438+
"SELECT MAX(timestamp), MAX(IIF(state=?,timestamp_sent,0))
14341439
FROM msgs
14351440
WHERE chat_id=? AND hidden=0 AND state>?
14361441
HAVING COUNT(*) > 0",
1437-
(self, MessageState::InFresh),
1442+
(MessageState::InSeen, self, MessageState::InFresh),
1443+
|row| {
1444+
let ts: i64 = row.get(0)?;
1445+
let ts_sent_seen: i64 = row.get(1)?;
1446+
Ok((ts, ts_sent_seen))
1447+
},
14381448
)
14391449
.await?
1450+
.and_then(|(ts, ts_sent_seen)| {
1451+
match incoming || ts_sent_seen <= message_timestamp {
1452+
true => Some(ts),
1453+
false => None,
1454+
}
1455+
})
14401456
} else {
14411457
None
14421458
};

src/receive_imf.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,11 +1255,13 @@ async fn add_parts(
12551255

12561256
let in_fresh = state == MessageState::InFresh;
12571257
let sort_to_bottom = false;
1258+
let received = true;
12581259
let sort_timestamp = chat_id
12591260
.calc_sort_timestamp(
12601261
context,
12611262
mime_parser.timestamp_sent,
12621263
sort_to_bottom,
1264+
received,
12631265
mime_parser.incoming,
12641266
)
12651267
.await?;

src/receive_imf/tests.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4811,6 +4811,36 @@ async fn test_protected_group_add_remove_member_missing_key() -> Result<()> {
48114811
Ok(())
48124812
}
48134813

4814+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4815+
async fn test_older_message_from_2nd_device() -> Result<()> {
4816+
let mut tcm = TestContextManager::new();
4817+
let alice = &tcm.alice().await;
4818+
let chat_id = alice
4819+
.create_chat_with_contact("", "bob@example.net")
4820+
.await
4821+
.id;
4822+
alice.send_text(chat_id, "We share this account").await;
4823+
let received = receive_imf(
4824+
alice,
4825+
b"From: alice@example.org\n\
4826+
To: bob@example.net\n\
4827+
Message-ID: <1234-2-4@example.org>\n\
4828+
Date: Sat, 07 Dec 1970 19:00:26 +0000\n\
4829+
\n\
4830+
I'm Alice too\n",
4831+
true,
4832+
)
4833+
.await?
4834+
.unwrap();
4835+
alice
4836+
.golden_test_chat(
4837+
received.chat_id,
4838+
"receive_imf_older_message_from_2nd_device",
4839+
)
4840+
.await;
4841+
Ok(())
4842+
}
4843+
48144844
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
48154845
async fn test_dont_create_adhoc_group_on_member_removal() -> Result<()> {
48164846
let mut tcm = TestContextManager::new();

src/securejoin/bob.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ pub(super) async fn start_protocol(context: &Context, invite: QrInvite) -> Resul
7373
// Calculate the sort timestamp before checking the chat protection status so that if we
7474
// race with its change, we don't add our message below the protection message.
7575
let sort_to_bottom = true;
76+
let (received, incoming) = (false, false);
7677
let ts_sort = chat_id
77-
.calc_sort_timestamp(context, 0, sort_to_bottom, false)
78+
.calc_sort_timestamp(context, 0, sort_to_bottom, received, incoming)
7879
.await?;
7980
if chat_id.is_protected(context).await? == ProtectionStatus::Unprotected {
8081
let ts_start = time();
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Single#Chat#10: bob@example.net [bob@example.net]
2+
--------------------------------------------------------------------------------
3+
Msg#10: Me (Contact#Contact#Self): We share this account √
4+
Msg#11: Me (Contact#Contact#Self): I'm Alice too √
5+
--------------------------------------------------------------------------------

0 commit comments

Comments
 (0)