Skip to main content

matrix_sdk_ui/timeline/controller/
read_receipts.rs

1// Copyright 2023 Kévin Commaille
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{cmp::Ordering, collections::HashMap};
16
17use futures_core::Stream;
18use indexmap::IndexMap;
19use ruma::{
20    EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, UserId,
21    events::receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
22};
23use tokio::sync::watch;
24use tokio_stream::wrappers::WatchStream;
25use tracing::{debug, error, instrument, trace, warn};
26
27use super::{
28    AllRemoteEvents, ObservableItemsTransaction, RelativePosition, RoomDataProvider,
29    TimelineMetadata, TimelineState, rfind_event_by_id,
30};
31use crate::timeline::{TimelineItem, controller::TimelineStateTransaction};
32
33/// In-memory caches for read receipts.
34#[derive(Clone, Debug, Default)]
35pub(super) struct ReadReceiptsState {
36    /// Map of public read receipts on events.
37    ///
38    /// Event ID => User ID => Read receipt of the user.
39    by_event: HashMap<OwnedEventId, IndexMap<OwnedUserId, Receipt>>,
40
41    /// In-memory cache of all latest read receipts by user.
42    ///
43    /// User ID => Receipt type => Read receipt of the user of the given
44    /// type.
45    latest_by_user: HashMap<OwnedUserId, HashMap<ReceiptType, (OwnedEventId, Receipt)>>,
46
47    /// A sender to notify of changes to the receipts of our own user.
48    own_user_read_receipts_changed_sender: watch::Sender<()>,
49}
50
51/// Whether to take local-only *implicit* read receipts into account when
52/// looking up a user's latest read receipt.
53///
54/// Implicit receipts are placed on the user's own events (see
55/// `maybe_add_implicit_read_receipt`) to keep the local notification count in
56/// sync, but they're never sent to the homeserver. They must be excluded when
57/// deciding whether an explicit receipt still needs to be sent, otherwise
58/// we'd skip sending one and the server would never recompute the push/badge
59/// count.
60#[derive(Clone, Copy, PartialEq, Eq, Debug)]
61pub(super) enum ImplicitReadReceipts {
62    /// Consider implicit read receipts (reads from the in-memory cache).
63    Include,
64    /// Ignore implicit read receipts (only considers receipts persisted to the
65    /// store).
66    Exclude,
67}
68
69impl ReadReceiptsState {
70    /// Empty the caches.
71    pub(super) fn clear(&mut self) {
72        self.by_event.clear();
73        self.latest_by_user.clear();
74    }
75
76    /// Subscribe to changes in the read receipts of our own user.
77    pub(super) fn subscribe_own_user_read_receipts_changed(
78        &self,
79    ) -> impl Stream<Item = ()> + use<> {
80        let subscriber = self.own_user_read_receipts_changed_sender.subscribe();
81        WatchStream::from_changes(subscriber)
82    }
83
84    /// Read the latest read receipt of the given type for the given user, from
85    /// the in-memory cache.
86    pub(crate) fn get_latest(
87        &self,
88        user_id: &UserId,
89        receipt_type: &ReceiptType,
90    ) -> Option<&(OwnedEventId, Receipt)> {
91        self.latest_by_user.get(user_id).and_then(|map| map.get(receipt_type))
92    }
93
94    /// Insert or update in the local cache the latest read receipt for the
95    /// given user.
96    fn upsert_latest(
97        &mut self,
98        user_id: OwnedUserId,
99        receipt_type: ReceiptType,
100        read_receipt: (OwnedEventId, Receipt),
101    ) {
102        self.latest_by_user.entry(user_id).or_default().insert(receipt_type, read_receipt);
103    }
104
105    /// Update the timeline items with the given read receipt if it is more
106    /// recent than the current one.
107    ///
108    /// In the process, if applicable, this method updates the inner maps to use
109    /// the new receipt. If `is_own_user_id` is `false`, it also updates the
110    /// receipts on the corresponding timeline items.
111    ///
112    /// Currently this method only works reliably if the timeline was started
113    /// from the end of the timeline.
114    #[instrument(skip_all, fields(user_id = %new_receipt.user_id, event_id = %new_receipt.event_id))]
115    fn maybe_update_read_receipt(
116        &mut self,
117        new_receipt: FullReceipt<'_>,
118        is_own_user_id: bool,
119        timeline_items: &mut ObservableItemsTransaction<'_>,
120    ) {
121        let all_events = timeline_items.all_remote_events();
122
123        // Get old receipt.
124        let old_receipt = self.get_latest(new_receipt.user_id, &new_receipt.receipt_type);
125
126        if old_receipt
127            .as_ref()
128            .is_some_and(|(old_receipt_event_id, _)| old_receipt_event_id == new_receipt.event_id)
129        {
130            // The receipt has not changed so there is nothing to do.
131            if !is_own_user_id {
132                trace!("receipt hasn't changed, nothing to do");
133            }
134            return;
135        }
136
137        let old_event_id = old_receipt.map(|(event_id, _)| event_id);
138
139        // Find receipts positions.
140        let mut old_receipt_pos = None;
141        let mut old_item_pos = None;
142        let mut old_item_event_id = None;
143        let mut new_receipt_pos = None;
144        let mut new_item_pos = None;
145        let mut new_item_event_id = None;
146
147        for (pos, event) in all_events.iter().rev().enumerate() {
148            if old_receipt_pos.is_none() && old_event_id == Some(&event.event_id) {
149                old_receipt_pos = Some(pos);
150            }
151
152            // The receipt should appear on the first visible event that can show read
153            // receipts.
154            if old_receipt_pos.is_some()
155                && old_item_event_id.is_none()
156                && event.visible
157                && event.can_show_read_receipts
158            {
159                old_item_pos = event.timeline_item_index;
160                old_item_event_id = Some(event.event_id.clone());
161            }
162
163            if new_receipt_pos.is_none() && new_receipt.event_id == event.event_id {
164                new_receipt_pos = Some(pos);
165            }
166
167            // The receipt should appear on the first visible event that can show read
168            // receipts.
169            if new_receipt_pos.is_some()
170                && new_item_event_id.is_none()
171                && event.visible
172                && event.can_show_read_receipts
173            {
174                new_item_pos = event.timeline_item_index;
175                new_item_event_id = Some(event.event_id.clone());
176            }
177
178            if old_item_event_id.is_some() && new_item_event_id.is_some() {
179                // We have everything we need, stop.
180                break;
181            }
182        }
183
184        // Check if the old receipt is more recent than the new receipt.
185        if let Some(old_receipt_pos) = old_receipt_pos {
186            let Some(new_receipt_pos) = new_receipt_pos else {
187                // The old receipt is more recent since we can't find the new receipt in the
188                // timeline and we supposedly have all events since the end of the timeline.
189                if !is_own_user_id {
190                    trace!(
191                        "we had a previous read receipt, but couldn't find the event \
192                         targeted by the new read receipt in the timeline, exiting"
193                    );
194                }
195                return;
196            };
197
198            if old_receipt_pos < new_receipt_pos {
199                // The old receipt is more recent than the new one.
200                if !is_own_user_id {
201                    trace!("the previous read receipt is more recent than the new one, exiting");
202                }
203                return;
204            }
205        }
206
207        // The new receipt is deemed more recent from now on because:
208        // - If old_receipt_pos is Some, we already checked all the cases where it
209        //   wouldn't be more recent.
210        // - If both old_receipt_pos and new_receipt_pos are None, they are both
211        //   explicit read receipts so the server should only send us a more recent
212        //   receipt.
213        // - If old_receipt_pos is None and new_receipt_pos is Some, the new receipt is
214        //   more recent because it has a place in the timeline.
215
216        if !is_own_user_id {
217            trace!(
218                from_event = ?old_event_id,
219                from_visible_event = ?old_item_event_id,
220                to_event = ?new_receipt.event_id,
221                to_visible_event = ?new_item_event_id,
222                ?old_item_pos,
223                ?new_item_pos,
224                "moving read receipt",
225            );
226
227            // Remove the old receipt from the old event.
228            if let Some(old_event_id) = old_event_id.cloned() {
229                self.remove_event_receipt_for_user(&old_event_id, new_receipt.user_id);
230            }
231
232            // Add the new receipt to the new event.
233            self.add_event_receipt_for_user(
234                new_receipt.event_id.to_owned(),
235                new_receipt.user_id.to_owned(),
236                new_receipt.receipt.clone(),
237            );
238        }
239
240        // Update the receipt of the user.
241        self.upsert_latest(
242            new_receipt.user_id.to_owned(),
243            new_receipt.receipt_type,
244            (new_receipt.event_id.to_owned(), new_receipt.receipt.clone()),
245        );
246
247        if is_own_user_id {
248            self.own_user_read_receipts_changed_sender.send_replace(());
249            // This receipt cannot change items in the timeline.
250            return;
251        }
252
253        if new_item_event_id == old_item_event_id {
254            // The receipt did not change in the timeline.
255            return;
256        }
257
258        let timeline_update = ReadReceiptTimelineUpdate {
259            old_item_pos,
260            old_event_id: old_item_event_id,
261            new_item_pos,
262            new_event_id: new_item_event_id,
263        };
264
265        timeline_update.apply(
266            timeline_items,
267            new_receipt.user_id.to_owned(),
268            new_receipt.receipt.clone(),
269        );
270    }
271
272    /// Returns the cached receipts by user for a given `event_id`.
273    fn get_event_receipts(&self, event_id: &EventId) -> Option<&IndexMap<OwnedUserId, Receipt>> {
274        self.by_event.get(event_id)
275    }
276
277    /// Mark the given event as seen by the user with the given receipt.
278    fn add_event_receipt_for_user(
279        &mut self,
280        event_id: OwnedEventId,
281        user_id: OwnedUserId,
282        receipt: Receipt,
283    ) {
284        self.by_event.entry(event_id).or_default().insert(user_id, receipt);
285    }
286
287    /// Unmark the given event as seen by the user.
288    fn remove_event_receipt_for_user(&mut self, event_id: &EventId, user_id: &UserId) {
289        if let Some(map) = self.by_event.get_mut(event_id) {
290            map.swap_remove(user_id);
291            // Remove the entire map if this was the last entry.
292            if map.is_empty() {
293                self.by_event.remove(event_id);
294            }
295        }
296    }
297
298    /// Get the read receipts by user for the given event.
299    ///
300    /// This includes all the receipts on the event as well as all the receipts
301    /// on the following events that are filtered out (not visible).
302    #[instrument(skip(self, timeline_items, at_end))]
303    pub(super) fn compute_event_receipts(
304        &self,
305        event_id: &EventId,
306        timeline_items: &mut ObservableItemsTransaction<'_>,
307        at_end: bool,
308    ) -> IndexMap<OwnedUserId, Receipt> {
309        let mut all_receipts = self.get_event_receipts(event_id).cloned().unwrap_or_default();
310
311        if at_end {
312            // No need to search for extra receipts, there are no events after.
313            trace!(
314                "early return because @end, retrieved receipts: {}",
315                all_receipts.iter().map(|(u, _)| u.as_str()).collect::<Vec<_>>().join(", ")
316            );
317            return all_receipts;
318        }
319
320        trace!(
321            "loaded receipts: {}",
322            all_receipts.iter().map(|(u, _)| u.as_str()).collect::<Vec<_>>().join(", ")
323        );
324
325        // We are going to add receipts for hidden events to this item.
326        //
327        // However, since we may be inserting an event at a random position, the
328        // previous timeline item may already be holding some hidden read
329        // receipts. As a result, we need to be careful here: if we're inserting
330        // after an event that holds hidden read receipts, then we should steal
331        // them from it.
332        //
333        // Find the event, go past it, and keep a reference to the previous rendered
334        // timeline item, if any.
335        let Some(current_event_index) = timeline_items.position_by_event_id(event_id) else {
336            warn!("Could not find event {event_id} in timeline");
337            return all_receipts;
338        };
339        let mut prev_events_iter = timeline_items.all_remote_events().range(0..current_event_index);
340        let previous_events_that_can_show_read_receipts =
341            prev_events_iter.by_ref().filter(|event| event.can_show_read_receipts).filter_map(
342                |event| event.timeline_item_index.map(|item_index| (&event.event_id, item_index)),
343            );
344
345        // Ok, the event we're searching for is the last item in our list.
346        //
347        // Let's just clone the event ID and copy the index to avoid double borrow of
348        // the `events_iter`.
349        let prev_event_and_item_index = previous_events_that_can_show_read_receipts
350            .last()
351            .map(|(event_id, index)| (event_id.clone(), index));
352
353        // Include receipts from the following events that are hidden or can't show
354        // read receipts until the next event that is visible and can show read
355        // receipts.
356
357        // Start by creating an iterator from the following event, if possible.
358        let next_events_iter =
359            timeline_items.all_remote_events().range(current_event_index..).skip(1);
360        let mut hidden = Vec::new();
361        for hidden_receipt_event_meta in
362            next_events_iter.take_while(|meta| !meta.visible || !meta.can_show_read_receipts)
363        {
364            if let Some(event_receipts) =
365                self.get_event_receipts(&hidden_receipt_event_meta.event_id)
366            {
367                trace!(%hidden_receipt_event_meta.event_id, "found receipts on hidden event");
368                hidden.extend(event_receipts.clone());
369            }
370        }
371
372        // Steal hidden receipts from the previous timeline item, if it carried them.
373        if let Some((prev_event_id, prev_item_index)) = prev_event_and_item_index {
374            let prev_item = &timeline_items[prev_item_index];
375            // Technically, we could unwrap the `as_event()`, because this is a rendered
376            // item for an event in all_remote_events, but this extra check is
377            // cheap.
378            if let Some(remote_prev_item) = prev_item.as_event() {
379                let prev_receipts = remote_prev_item.read_receipts().clone();
380                for (user_id, _) in &hidden {
381                    if !prev_receipts.contains_key(user_id) {
382                        continue;
383                    }
384                    let mut up = ReadReceiptTimelineUpdate {
385                        old_item_pos: Some(prev_item_index),
386                        old_event_id: Some(prev_event_id.clone()),
387                        new_item_pos: None,
388                        new_event_id: None,
389                    };
390                    up.remove_old_receipt(timeline_items, user_id);
391                }
392            }
393        }
394
395        all_receipts.extend(hidden);
396        trace!(
397            "computed receipts: {}",
398            all_receipts.iter().map(|(u, _)| u.as_str()).collect::<Vec<_>>().join(", ")
399        );
400        all_receipts
401    }
402}
403
404struct FullReceipt<'a> {
405    event_id: &'a EventId,
406    user_id: &'a UserId,
407    receipt_type: ReceiptType,
408    receipt: &'a Receipt,
409}
410
411/// A read receipt update in the timeline.
412#[derive(Clone, Debug, Default)]
413struct ReadReceiptTimelineUpdate {
414    /// The position of the timeline item that had the old receipt of the user,
415    /// if any.
416    old_item_pos: Option<usize>,
417    /// The old event that had the receipt of the user, if any.
418    old_event_id: Option<OwnedEventId>,
419    /// The position of the timeline item that has the new receipt of the user,
420    /// if any.
421    new_item_pos: Option<usize>,
422    /// The new event that has the receipt of the user, if any.
423    new_event_id: Option<OwnedEventId>,
424}
425
426impl ReadReceiptTimelineUpdate {
427    /// Remove the old receipt from the corresponding timeline item.
428    #[instrument(skip_all)]
429    fn remove_old_receipt(&mut self, items: &mut ObservableItemsTransaction<'_>, user_id: &UserId) {
430        let Some(event_id) = &self.old_event_id else {
431            // Nothing to do.
432            return;
433        };
434
435        let item_pos = self.old_item_pos.or_else(|| {
436            items
437                .iter_remotes_region()
438                .rev()
439                .filter_map(|(nth, item)| Some((nth, item.as_event()?)))
440                .find_map(|(nth, event_item)| {
441                    (event_item.event_id() == Some(event_id)).then_some(nth)
442                })
443        });
444
445        let Some(item_pos) = item_pos else {
446            debug!(%event_id, %user_id, "inconsistent state: old event item for read receipt was not found");
447            return;
448        };
449
450        self.old_item_pos = Some(item_pos);
451
452        let event_item = &items[item_pos];
453        let event_item_id = event_item.unique_id().to_owned();
454
455        let Some(mut event_item) = event_item.as_event().cloned() else {
456            warn!("received a read receipt for a virtual item, this should not be possible");
457            return;
458        };
459
460        if let Some(remote_event_item) = event_item.as_remote_mut() {
461            if remote_event_item.read_receipts.swap_remove(user_id).is_none() {
462                debug!(
463                    %event_id, %user_id,
464                    "inconsistent state: old event item for user's read \
465                     receipt doesn't have a receipt for the user"
466                );
467            }
468            trace!(%user_id, %event_id, "removed read receipt from event item");
469            items.replace(item_pos, TimelineItem::new(event_item, event_item_id));
470        } else {
471            warn!("received a read receipt for a local item, this should not be possible");
472        }
473    }
474
475    /// Add the new receipt to the corresponding timeline item.
476    #[instrument(skip_all)]
477    fn add_new_receipt(
478        self,
479        items: &mut ObservableItemsTransaction<'_>,
480        user_id: OwnedUserId,
481        receipt: Receipt,
482    ) {
483        let Some(event_id) = self.new_event_id else {
484            // Nothing to do.
485            return;
486        };
487
488        let old_item_pos = self.old_item_pos.unwrap_or(0);
489
490        let item_pos = self.new_item_pos.or_else(|| {
491            items
492                .iter_remotes_region()
493                // Don't iterate over all items if the `old_item_pos` is known: the `item_pos`
494                // for the new item is necessarily _after_ the old item.
495                .skip_while(|(nth, _)| *nth < old_item_pos)
496                .find_map(|(nth, item)| {
497                    if let Some(event_item) = item.as_event() {
498                        (event_item.event_id() == Some(&event_id)).then_some(nth)
499                    } else {
500                        None
501                    }
502                })
503        });
504
505        let Some(item_pos) = item_pos else {
506            debug!(
507                %event_id, %user_id,
508                "inconsistent state: new event item for read receipt was not found",
509            );
510            return;
511        };
512
513        debug_assert!(
514            item_pos >= self.old_item_pos.unwrap_or(0),
515            "The new receipt must be added on a timeline item that is _after_ the timeline item \
516             that was holding the old receipt"
517        );
518
519        let event_item = &items[item_pos];
520        let event_item_id = event_item.unique_id().to_owned();
521
522        let Some(mut event_item) = event_item.as_event().cloned() else {
523            warn!("received a read receipt for a virtual item, this should not be possible");
524            return;
525        };
526
527        if let Some(remote_event_item) = event_item.as_remote_mut() {
528            trace!(%user_id, %event_id, "added read receipt to event item");
529            remote_event_item.read_receipts.insert(user_id, receipt);
530            items.replace(item_pos, TimelineItem::new(event_item, event_item_id));
531        } else {
532            warn!("received a read receipt for a local item, this should not be possible");
533        }
534    }
535
536    /// Apply this update to the timeline.
537    fn apply(
538        mut self,
539        items: &mut ObservableItemsTransaction<'_>,
540        user_id: OwnedUserId,
541        receipt: Receipt,
542    ) {
543        self.remove_old_receipt(items, &user_id);
544        self.add_new_receipt(items, user_id, receipt);
545    }
546}
547
548impl<P: RoomDataProvider> TimelineStateTransaction<'_, P> {
549    pub(super) fn handle_explicit_read_receipts(
550        &mut self,
551        receipt_event_content: ReceiptEventContent,
552        own_user_id: &UserId,
553    ) {
554        trace!("handling explicit read receipts");
555
556        for (event_id, receipt_types) in receipt_event_content.0 {
557            for (receipt_type, receipts) in receipt_types {
558                // Discard the read marker updates in this function.
559                if !matches!(receipt_type, ReceiptType::Read | ReceiptType::ReadPrivate) {
560                    continue;
561                }
562
563                for (user_id, receipt) in receipts {
564                    let is_own_user_id = user_id == own_user_id;
565                    let full_receipt = FullReceipt {
566                        event_id: &event_id,
567                        user_id: &user_id,
568                        receipt_type: receipt_type.clone(),
569                        receipt: &receipt,
570                    };
571
572                    self.meta.read_receipts.maybe_update_read_receipt(
573                        full_receipt,
574                        is_own_user_id,
575                        &mut self.items,
576                    );
577                }
578            }
579        }
580    }
581
582    /// Load the read receipts from the store for the given event ID.
583    ///
584    /// Populates the read receipts in-memory caches.
585    pub(super) async fn load_read_receipts_for_event(
586        &mut self,
587        event_id: &EventId,
588        room_data_provider: &P,
589    ) {
590        trace!(%event_id, "loading initial receipts for an event");
591
592        let receipt_thread = self.focus.receipt_thread();
593
594        let receipts = if matches!(receipt_thread, ReceiptThread::Unthreaded | ReceiptThread::Main)
595        {
596            // If the requested receipt thread is unthreaded or main, we maintain maximal
597            // compatibility with clients using either unthreaded or main-thread read
598            // receipts by allowing both here.
599
600            // First, load the main receipts.
601            let mut main_receipts =
602                room_data_provider.load_event_receipts(event_id, &ReceiptThread::Main).await;
603
604            // Then, load the unthreaded receipts.
605            let unthreaded_receipts =
606                room_data_provider.load_event_receipts(event_id, &ReceiptThread::Unthreaded).await;
607
608            // We can safely extend both here: if a key is already set, then that means that
609            // the user has the unthreaded and main receipt on the main event,
610            // which is fine, and something we display as the one user receipt.
611            main_receipts.extend(unthreaded_receipts);
612            main_receipts
613        } else {
614            // In all other cases, return what's requested, and only that (threaded
615            // receipts).
616            room_data_provider.load_event_receipts(event_id, &receipt_thread).await
617        };
618
619        let own_user_id = room_data_provider.own_user_id();
620
621        // Since they are explicit read receipts, we need to check if they are
622        // superseded by implicit read receipts.
623        for (user_id, receipt) in receipts {
624            let full_receipt = FullReceipt {
625                event_id,
626                user_id: &user_id,
627                receipt_type: ReceiptType::Read,
628                receipt: &receipt,
629            };
630
631            self.meta.read_receipts.maybe_update_read_receipt(
632                full_receipt,
633                user_id == own_user_id,
634                &mut self.items,
635            );
636        }
637    }
638
639    /// Add an implicit read receipt to the given event item, if it is more
640    /// recent than the current read receipt for the sender of the event.
641    ///
642    /// According to the spec, read receipts should not point to events sent by
643    /// our own user, but these events are used to reset the notification
644    /// count, so we need to handle them locally too. For that we create an
645    /// "implicit" read receipt, compared to the "explicit" ones sent by the
646    /// client.
647    pub(super) fn maybe_add_implicit_read_receipt(
648        &mut self,
649        event_id: &EventId,
650        sender: Option<&UserId>,
651        timestamp: Option<MilliSecondsSinceUnixEpoch>,
652    ) {
653        let (Some(user_id), Some(timestamp)) = (sender, timestamp) else {
654            // We cannot add a read receipt if we do not know the user or the timestamp.
655            return;
656        };
657
658        trace!(%user_id, %event_id, "adding implicit read receipt");
659
660        let mut receipt = Receipt::new(timestamp);
661        receipt.thread = self.focus.receipt_thread();
662
663        let full_receipt =
664            FullReceipt { event_id, user_id, receipt_type: ReceiptType::Read, receipt: &receipt };
665
666        let is_own_event = sender.is_some_and(|sender| sender == self.meta.own_user_id);
667
668        self.meta.read_receipts.maybe_update_read_receipt(
669            full_receipt,
670            is_own_event,
671            &mut self.items,
672        );
673    }
674
675    /// Update the read receipts on the event with the given event ID and the
676    /// previous visible event because of a visibility change.
677    #[instrument(skip(self))]
678    pub(super) fn maybe_update_read_receipts_of_prev_event(&mut self, event_id: &EventId) {
679        // Find the previous visible event, if there is one.
680        let Some(prev_event_meta) = self
681            .items
682            .all_remote_events()
683            .iter()
684            .rev()
685            // Find the event item.
686            .skip_while(|meta| meta.event_id != event_id)
687            // Go past the event item.
688            .skip(1)
689            // Find the first visible item that can show read receipts.
690            .find(|meta| meta.visible && meta.can_show_read_receipts)
691        else {
692            trace!("Couldn't find any previous visible event, exiting");
693            return;
694        };
695
696        let Some((prev_item_pos, prev_event_item)) =
697            rfind_event_by_id(&self.items, &prev_event_meta.event_id)
698        else {
699            error!("inconsistent state: timeline item of visible event was not found");
700            return;
701        };
702
703        let prev_event_item_id = prev_event_item.internal_id.to_owned();
704        let mut prev_event_item = prev_event_item.clone();
705
706        let Some(remote_prev_event_item) = prev_event_item.as_remote_mut() else {
707            warn!("loading read receipts for a local item, this should not be possible");
708            return;
709        };
710
711        let read_receipts = self.meta.read_receipts.compute_event_receipts(
712            &remote_prev_event_item.event_id,
713            &mut self.items,
714            false,
715        );
716
717        // If the count did not change, the receipts did not change either.
718        if read_receipts.len() == remote_prev_event_item.read_receipts.len() {
719            trace!("same count of read receipts, not doing anything");
720            return;
721        }
722
723        trace!("replacing read receipts with the new ones");
724        remote_prev_event_item.read_receipts = read_receipts;
725        self.items.replace(prev_item_pos, TimelineItem::new(prev_event_item, prev_event_item_id));
726    }
727}
728
729impl<P: RoomDataProvider> TimelineState<P> {
730    /// Populates our own latest read receipt in the in-memory by-user read
731    /// receipt cache.
732    pub(super) async fn populate_initial_user_receipt(
733        &mut self,
734        room_data_provider: &P,
735        receipt_type: ReceiptType,
736    ) {
737        let own_user_id = room_data_provider.own_user_id().to_owned();
738
739        let receipt_thread = self.focus.receipt_thread();
740        let wants_unthreaded_receipts = receipt_thread == ReceiptThread::Unthreaded;
741
742        let mut read_receipt = room_data_provider
743            .load_user_receipt(receipt_type.clone(), &receipt_thread, &own_user_id)
744            .await;
745
746        if wants_unthreaded_receipts && read_receipt.is_none() {
747            // Fallback to the one in the main thread.
748            read_receipt = room_data_provider
749                .load_user_receipt(receipt_type.clone(), &ReceiptThread::Main, &own_user_id)
750                .await;
751        }
752
753        if let Some(read_receipt) = read_receipt {
754            self.meta.read_receipts.upsert_latest(own_user_id, receipt_type, read_receipt);
755        }
756    }
757
758    /// Get the latest read receipt for the given user.
759    ///
760    /// Useful to get the latest read receipt, whether it's private or public.
761    pub(super) async fn latest_user_read_receipt(
762        &self,
763        user_id: &UserId,
764        receipt_thread: ReceiptThread,
765        room_data_provider: &P,
766        implicit_receipts: ImplicitReadReceipts,
767    ) -> Option<(OwnedEventId, Receipt)> {
768        let all_remote_events = self.items.all_remote_events();
769
770        let public_read_receipt = self
771            .meta
772            .user_receipt(
773                user_id,
774                ReceiptType::Read,
775                receipt_thread.clone(),
776                room_data_provider,
777                all_remote_events,
778                implicit_receipts,
779            )
780            .await;
781
782        let private_read_receipt = self
783            .meta
784            .user_receipt(
785                user_id,
786                ReceiptType::ReadPrivate,
787                receipt_thread,
788                room_data_provider,
789                all_remote_events,
790                implicit_receipts,
791            )
792            .await;
793
794        // Let's assume that a private read receipt should be more recent than a public
795        // read receipt (otherwise there's no point in the private read receipt),
796        // and use it as the default.
797        match TimelineMetadata::compare_optional_receipts(
798            public_read_receipt.as_ref(),
799            private_read_receipt.as_ref(),
800            all_remote_events,
801        ) {
802            Ordering::Greater => public_read_receipt,
803            Ordering::Less => private_read_receipt,
804            _ => unreachable!(),
805        }
806    }
807
808    /// Get the ID of the visible timeline event with the latest read receipt
809    /// for the given user.
810    pub(super) fn latest_user_read_receipt_timeline_event_id(
811        &self,
812        user_id: &UserId,
813    ) -> Option<OwnedEventId> {
814        // We only need to use the local map, since receipts for known events are
815        // already loaded from the store.
816        let public_read_receipt = self.meta.read_receipts.get_latest(user_id, &ReceiptType::Read);
817        let private_read_receipt =
818            self.meta.read_receipts.get_latest(user_id, &ReceiptType::ReadPrivate);
819
820        // Let's assume that a private read receipt should be more recent than a public
821        // read receipt, otherwise there's no point in the private read receipt,
822        // and use it as default.
823        let (latest_receipt_id, _) = match TimelineMetadata::compare_optional_receipts(
824            public_read_receipt,
825            private_read_receipt,
826            self.items.all_remote_events(),
827        ) {
828            Ordering::Greater => public_read_receipt?,
829            Ordering::Less => private_read_receipt?,
830            _ => unreachable!(),
831        };
832
833        // Find the corresponding visible event.
834        self.items
835            .all_remote_events()
836            .iter()
837            .rev()
838            .skip_while(|ev| ev.event_id != *latest_receipt_id)
839            .find(|ev| ev.visible && ev.can_show_read_receipts)
840            .map(|ev| ev.event_id.clone())
841    }
842}
843
844impl TimelineMetadata {
845    /// Get the latest receipt of the given type for the given user in the
846    /// timeline.
847    ///
848    /// This will attempt to read the latest user receipt for a user from the
849    /// cache, or load it from the storage if missing from the cache.
850    ///
851    /// If the `ReceiptThread` is `Unthreaded`, it will try to find either the
852    /// unthreaded or the main-thread read receipt, to be maximally
853    /// compatible with clients using one or the other. Otherwise, it will
854    /// select only the receipts for that specific thread.
855    pub(super) async fn user_receipt<P: RoomDataProvider>(
856        &self,
857        user_id: &UserId,
858        receipt_type: ReceiptType,
859        receipt_thread: ReceiptThread,
860        room_data_provider: &P,
861        all_remote_events: &AllRemoteEvents,
862        implicit_receipts: ImplicitReadReceipts,
863    ) -> Option<(OwnedEventId, Receipt)> {
864        if implicit_receipts == ImplicitReadReceipts::Include // Only check the in-memory cache when implicit receipts are included.
865            && let Some(receipt) = self.read_receipts.get_latest(user_id, &receipt_type)
866        {
867            // Since it is in the timeline, it should be the most recent.
868            return Some(receipt.clone());
869        }
870
871        if receipt_thread == ReceiptThread::Unthreaded {
872            // Maintain compatibility with clients using either the unthreaded and main read
873            // receipts, and try to find the most recent one.
874            let unthreaded_read_receipt = room_data_provider
875                .load_user_receipt(receipt_type.clone(), &ReceiptThread::Unthreaded, user_id)
876                .await;
877
878            let main_thread_read_receipt = room_data_provider
879                .load_user_receipt(receipt_type.clone(), &ReceiptThread::Main, user_id)
880                .await;
881
882            // Let's use the unthreaded read receipt as default, since it's the one we
883            // should be using.
884            match Self::compare_optional_receipts(
885                main_thread_read_receipt.as_ref(),
886                unthreaded_read_receipt.as_ref(),
887                all_remote_events,
888            ) {
889                Ordering::Greater => main_thread_read_receipt,
890                Ordering::Less => unthreaded_read_receipt,
891                _ => unreachable!(),
892            }
893        } else {
894            // In all the other cases, use the thread's read receipt. A main-thread receipt
895            // in particular will use this code path, and not be compatible with
896            // an unthreaded read receipt.
897            room_data_provider
898                .load_user_receipt(receipt_type.clone(), &receipt_thread, user_id)
899                .await
900        }
901    }
902
903    /// Compares two optional receipts to know which one is more recent.
904    ///
905    /// Returns `Ordering::Greater` if the left-hand side is more recent than
906    /// the right-hand side, and `Ordering::Less` if it is older. If it's
907    /// not possible to know which one is the more recent, defaults to
908    /// `Ordering::Less`, making the right-hand side the default.
909    fn compare_optional_receipts(
910        lhs: Option<&(OwnedEventId, Receipt)>,
911        rhs_or_default: Option<&(OwnedEventId, Receipt)>,
912        all_remote_events: &AllRemoteEvents,
913    ) -> Ordering {
914        // If we only have one, use it.
915        let Some((lhs_event_id, lhs_receipt)) = lhs else {
916            return Ordering::Less;
917        };
918        let Some((rhs_event_id, rhs_receipt)) = rhs_or_default else {
919            return Ordering::Greater;
920        };
921
922        // Compare by position in the timeline.
923        if let Some(relative_pos) =
924            Self::compare_events_positions(lhs_event_id, rhs_event_id, all_remote_events)
925        {
926            if relative_pos == RelativePosition::Before {
927                return Ordering::Greater;
928            }
929
930            return Ordering::Less;
931        }
932
933        // Compare by timestamp.
934        if let Some((lhs_ts, rhs_ts)) = lhs_receipt.ts.zip(rhs_receipt.ts) {
935            if lhs_ts > rhs_ts {
936                return Ordering::Greater;
937            }
938
939            return Ordering::Less;
940        }
941
942        Ordering::Less
943    }
944}