1use std::{
16 collections::BTreeSet,
17 fmt,
18 ops::Deref,
19 sync::{Arc, OnceLock},
20};
21
22use as_variant::as_variant;
23use eyeball_im::{VectorDiff, VectorSubscriberStream};
24use eyeball_im_util::vector::{FilterMap, VectorObserverExt};
25use futures_core::Stream;
26use imbl::{HashSet, Vector};
27use matrix_sdk::{
28 deserialized_responses::TimelineEvent,
29 event_cache::{
30 DecryptionRetryRequest, EventCache, EventFocusedCache, PaginationStatus, PinnedEventsCache,
31 RoomEventCache, Subscriber as EventCacheSubscriber, ThreadEventCache, TimelineVectorDiffs,
32 },
33 send_queue::{
34 LocalEcho, LocalEchoContent, RoomSendQueueUpdate, SendHandle, SendReactionHandle,
35 },
36 task_monitor::BackgroundTaskHandle,
37};
38#[cfg(test)]
39use ruma::events::receipt::ReceiptEventContent;
40use ruma::{
41 EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
42 TransactionId, UserId,
43 api::client::receipt::create_receipt::v3::ReceiptType as SendReceiptType,
44 events::{
45 AnyMessageLikeEventContent, AnySyncEphemeralRoomEvent, AnySyncMessageLikeEvent,
46 AnySyncTimelineEvent, MessageLikeEventType,
47 poll::unstable_start::UnstablePollStartEventContent,
48 reaction::ReactionEventContent,
49 receipt::{Receipt, ReceiptThread, ReceiptType},
50 relation::Annotation,
51 room::message::{MessageType, Relation},
52 },
53 room_version_rules::RoomVersionRules,
54 serde::Raw,
55};
56use tokio::sync::{RwLock, RwLockWriteGuard};
57use tracing::{
58 Instrument as _, Span, debug, error, field::debug, info, info_span, instrument, trace, warn,
59};
60
61pub(super) use self::{
62 metadata::{RelativePosition, TimelineMetadata},
63 observable_items::{
64 AllRemoteEvents, ObservableItems, ObservableItemsEntry, ObservableItemsTransaction,
65 ObservableItemsTransactionEntry,
66 },
67 state::TimelineState,
68 state_transaction::TimelineStateTransaction,
69};
70use super::{
71 DateDividerMode, EmbeddedEvent, Error, EventSendState, EventTimelineItem, InReplyToDetails,
72 MediaUploadProgress, Profile, TimelineDetails, TimelineEventItemId, TimelineFocus,
73 TimelineItem, TimelineItemContent, TimelineItemKind, TimelineReadReceiptTracking,
74 VirtualTimelineItem,
75 algorithms::{rfind_event_by_id, rfind_event_item},
76 event_item::{ReactionStatus, RemoteEventOrigin},
77 item::TimelineUniqueId,
78 subscriber::TimelineSubscriber,
79 traits::RoomDataProvider,
80};
81use crate::{
82 timeline::{
83 MsgLikeContent, MsgLikeKind, Room, TimelineEventFilterFn, TimelineEventFocusThreadMode,
84 algorithms::rfind_event_by_item_id,
85 controller::decryption_retry_task::compute_redecryption_candidates,
86 date_dividers::DateDividerAdjuster,
87 event_item::TimelineItemHandle,
88 tasks::{event_focused_task, pinned_events_task, thread_updates_task},
89 },
90 unable_to_decrypt_hook::UtdHookManager,
91};
92
93pub(in crate::timeline) mod aggregations;
94mod decryption_retry_task;
95mod metadata;
96mod observable_items;
97mod read_receipts;
98mod state;
99mod state_transaction;
100
101pub(super) use aggregations::*;
102pub(super) use decryption_retry_task::{CryptoDropHandles, spawn_crypto_tasks};
103use matrix_sdk_base::{CallIntentConsensus, RoomInfo};
104
105#[derive(Debug)]
111pub(in crate::timeline) enum TimelineFocusKind {
112 Live {
114 hide_threaded_events: bool,
116
117 event_cache: RoomEventCache,
119 },
120
121 Event {
124 focused_event_id: OwnedEventId,
126
127 thread_root: OnceLock<OwnedEventId>,
133
134 thread_mode: TimelineEventFocusThreadMode,
137
138 event_cache: EventFocusedCache,
140 },
141
142 Thread {
144 root_event_id: OwnedEventId,
146
147 event_cache: ThreadEventCache,
149 },
150
151 PinnedEvents {
152 event_cache: PinnedEventsCache,
154 },
155}
156
157impl TimelineFocusKind {
158 pub(super) fn receipt_thread(&self) -> ReceiptThread {
165 if let Some(thread_root) = self.thread_root() {
166 ReceiptThread::Thread(thread_root.to_owned())
167 } else if self.hide_threaded_events() {
168 ReceiptThread::Main
169 } else {
170 ReceiptThread::Unthreaded
171 }
172 }
173
174 fn hide_threaded_events(&self) -> bool {
176 match self {
177 TimelineFocusKind::Live { hide_threaded_events, .. } => *hide_threaded_events,
178 TimelineFocusKind::Event { thread_mode, .. } => {
179 matches!(
180 thread_mode,
181 TimelineEventFocusThreadMode::Automatic { hide_threaded_events: true }
182 )
183 }
184 TimelineFocusKind::Thread { .. } | TimelineFocusKind::PinnedEvents { .. } => false,
185 }
186 }
187
188 fn is_thread(&self) -> bool {
191 self.thread_root().is_some()
192 }
193
194 fn thread_root(&self) -> Option<&EventId> {
196 match self {
197 TimelineFocusKind::Event { thread_root, .. } => thread_root.get().map(|v| &**v),
198 TimelineFocusKind::Live { .. } | TimelineFocusKind::PinnedEvents { .. } => None,
199 TimelineFocusKind::Thread { root_event_id, .. } => Some(root_event_id),
200 }
201 }
202}
203
204#[derive(Clone, Debug)]
205pub(super) struct TimelineController<P: RoomDataProvider = Room> {
206 state: Arc<RwLock<TimelineState<P>>>,
208
209 focus: Arc<TimelineFocusKind>,
211
212 pub(crate) room_data_provider: P,
217
218 pub(super) settings: TimelineSettings,
220}
221
222#[derive(Clone)]
223pub(super) struct TimelineSettings {
224 pub(super) track_read_receipts: TimelineReadReceiptTracking,
227
228 pub(super) event_filter: Arc<TimelineEventFilterFn>,
231
232 pub(super) add_failed_to_parse: bool,
234
235 pub(super) date_divider_mode: DateDividerMode,
237}
238
239#[cfg(not(tarpaulin_include))]
240impl fmt::Debug for TimelineSettings {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 f.debug_struct("TimelineSettings")
243 .field("track_read_receipts", &self.track_read_receipts)
244 .field("add_failed_to_parse", &self.add_failed_to_parse)
245 .finish_non_exhaustive()
246 }
247}
248
249impl Default for TimelineSettings {
250 fn default() -> Self {
251 Self {
252 track_read_receipts: TimelineReadReceiptTracking::Disabled,
253 event_filter: Arc::new(default_event_filter),
254 add_failed_to_parse: true,
255 date_divider_mode: DateDividerMode::Daily,
256 }
257 }
258}
259
260pub fn default_event_filter(event: &AnySyncTimelineEvent, rules: &RoomVersionRules) -> bool {
270 match event {
271 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(ev)) => {
272 if ev.redacts(&rules.redaction).is_some() {
273 false
276 } else {
277 ev.event_type() != MessageLikeEventType::Reaction
280 }
281 }
282
283 AnySyncTimelineEvent::MessageLike(msg) => {
284 match msg.original_content() {
285 None => {
286 msg.event_type() != MessageLikeEventType::Reaction
289 }
290
291 Some(original_content) => {
292 match original_content {
293 AnyMessageLikeEventContent::RoomMessage(content) => {
294 if content
295 .relates_to
296 .as_ref()
297 .is_some_and(|rel| matches!(rel, Relation::Replacement(_)))
298 {
299 return false;
301 }
302
303 match content.msgtype {
304 MessageType::Audio(_)
305 | MessageType::Emote(_)
306 | MessageType::File(_)
307 | MessageType::Image(_)
308 | MessageType::Location(_)
309 | MessageType::Notice(_)
310 | MessageType::ServerNotice(_)
311 | MessageType::Text(_)
312 | MessageType::Video(_)
313 | MessageType::VerificationRequest(_) => true,
314 #[cfg(feature = "unstable-msc4274")]
315 MessageType::Gallery(_) => true,
316 _ => false,
317 }
318 }
319
320 AnyMessageLikeEventContent::Sticker(_)
321 | AnyMessageLikeEventContent::UnstablePollStart(
322 UnstablePollStartEventContent::New(_),
323 )
324 | AnyMessageLikeEventContent::CallInvite(_)
325 | AnyMessageLikeEventContent::RtcNotification(_)
326 | AnyMessageLikeEventContent::RoomEncrypted(_) => true,
327
328 AnyMessageLikeEventContent::Beacon(_) => false,
332 AnyMessageLikeEventContent::RtcDecline(_) => false,
335
336 _ => false,
337 }
338 }
339 }
340 }
341
342 AnySyncTimelineEvent::State(_) => {
343 true
345 }
346 }
347}
348
349pub(super) struct InitFocusResult {
351 pub has_events: bool,
353 pub focus_task: Option<BackgroundTaskHandle>,
356}
357
358#[derive(Clone, Debug, PartialEq)]
360pub struct ActiveCallInfo {
361 pub active_members: HashSet<OwnedUserId>,
363 pub call_intent: CallIntentConsensus,
365 pub is_joined: bool,
368 pub call_started_ts_millis: Option<MilliSecondsSinceUnixEpoch>,
372}
373
374impl ActiveCallInfo {
375 pub(crate) fn from_info(room_info: RoomInfo, owned_user_id: OwnedUserId) -> Option<Self> {
376 if room_info.has_active_room_call() {
377 Some(ActiveCallInfo {
378 active_members: HashSet::from(room_info.active_room_call_participants()),
379 call_intent: room_info.active_room_call_consensus_intent(),
380 is_joined: room_info.active_room_call_participants().contains(&owned_user_id),
381 call_started_ts_millis: None,
382 })
383 } else {
384 None
385 }
386 }
387
388 pub(crate) fn with_start_time(self, timestamp: Option<MilliSecondsSinceUnixEpoch>) -> Self {
389 Self { call_started_ts_millis: timestamp, ..self }
390 }
391}
392
393impl<P: RoomDataProvider> TimelineController<P> {
394 pub(super) async fn new(
395 room_data_provider: P,
396 focus: &TimelineFocus,
397 event_cache: &EventCache,
398 internal_id_prefix: Option<String>,
399 unable_to_decrypt_hook: Option<Arc<UtdHookManager>>,
400 is_room_encrypted: bool,
401 settings: TimelineSettings,
402 ) -> Result<Self, Error> {
403 let room_id = room_data_provider.room_id();
404
405 let focus = match focus {
406 TimelineFocus::Live { hide_threaded_events } => TimelineFocusKind::Live {
407 hide_threaded_events: *hide_threaded_events,
408 event_cache: event_cache.room(room_id).await?.0,
409 },
410
411 TimelineFocus::Event { target, thread_mode, num_context_events, .. } => {
412 TimelineFocusKind::Event {
413 event_cache: event_cache
414 .event_focused(room_id, target, (*thread_mode).into(), *num_context_events)
415 .await?
416 .0,
417 focused_event_id: target.clone(),
418 thread_root: OnceLock::new(),
420 thread_mode: *thread_mode,
421 }
422 }
423
424 TimelineFocus::Thread { root_event_id, .. } => TimelineFocusKind::Thread {
425 event_cache: event_cache.thread(room_id, root_event_id).await?.0,
426 root_event_id: root_event_id.clone(),
427 },
428
429 TimelineFocus::PinnedEvents => TimelineFocusKind::PinnedEvents {
430 event_cache: event_cache.pinned_events(room_id).await?.0,
431 },
432 };
433
434 let focus = Arc::new(focus);
435 let state = Arc::new(RwLock::new(TimelineState::new(
436 focus.clone(),
437 room_data_provider.own_user_id().to_owned(),
438 room_data_provider.room_version_rules(),
439 internal_id_prefix,
440 unable_to_decrypt_hook,
441 is_room_encrypted,
442 None,
443 )));
444
445 Ok(Self { state, focus, room_data_provider, settings })
446 }
447
448 pub async fn handle_encryption_state_changes(&self) {
453 let mut room_info = self.room_data_provider.room_info();
454
455 let mark_encrypted = || async {
457 let mut state = self.state.write().await;
458 state.meta.is_room_encrypted = true;
459 state.mark_all_events_as_encrypted();
460 };
461
462 if room_info.get().encryption_state().is_encrypted() {
463 mark_encrypted().await;
466 return;
467 }
468
469 while let Some(info) = room_info.next().await {
470 if info.encryption_state().is_encrypted() {
471 mark_encrypted().await;
472 break;
475 }
476 }
477 }
478
479 pub(super) async fn live_lazy_paginate_backwards(&self, num_events: u16) -> Option<usize> {
488 let state = self.state.read().await;
489
490 let (count, needs) = state
491 .meta
492 .subscriber_skip_count
493 .compute_next_when_paginating_backwards(num_events.into());
494
495 let is_live_timeline = true;
497 state.meta.subscriber_skip_count.update(count, is_live_timeline);
498
499 needs
500 }
501
502 pub(super) fn is_live(&self) -> bool {
504 matches!(&*self.focus, TimelineFocusKind::Live { .. })
505 }
506
507 pub(super) fn is_threaded(&self) -> bool {
509 self.focus.is_thread()
510 }
511
512 pub(super) fn thread_root(&self) -> Option<OwnedEventId> {
515 self.focus.thread_root().map(ToOwned::to_owned)
516 }
517
518 pub(super) async fn items(&self) -> Vector<Arc<TimelineItem>> {
522 self.state.read().await.items.clone_items()
523 }
524
525 #[cfg(test)]
526 pub(super) async fn subscribe_raw(
527 &self,
528 ) -> (Vector<Arc<TimelineItem>>, VectorSubscriberStream<Arc<TimelineItem>>) {
529 self.state.read().await.items.subscribe().into_values_and_stream()
530 }
531
532 pub(super) async fn subscribe(&self) -> (Vector<Arc<TimelineItem>>, TimelineSubscriber) {
533 let state = self.state.read().await;
534
535 TimelineSubscriber::new(&state.items, &state.meta.subscriber_skip_count)
536 }
537
538 pub(super) async fn subscribe_filter_map<U, F>(
539 &self,
540 f: F,
541 ) -> (Vector<U>, FilterMap<VectorSubscriberStream<Arc<TimelineItem>>, F>)
542 where
543 U: Clone,
544 F: Fn(Arc<TimelineItem>) -> Option<U>,
545 {
546 self.state.read().await.items.subscribe().filter_map(f)
547 }
548
549 #[instrument(skip_all)]
553 pub(super) async fn toggle_reaction_local(
554 &self,
555 item_id: &TimelineEventItemId,
556 key: &str,
557 ) -> Result<bool, Error> {
558 let mut state = self.state.write().await;
559
560 let Some((item_pos, item)) = rfind_event_by_item_id(&state.items, item_id) else {
561 warn!("Timeline item not found, can't add reaction");
562 return Err(Error::FailedToToggleReaction);
563 };
564
565 let user_id = self.room_data_provider.own_user_id();
566 let prev_status = item
567 .content()
568 .reactions()
569 .and_then(|map| Some(map.get(key)?.get(user_id)?.status.clone()));
570
571 let Some(prev_status) = prev_status else {
572 match item.handle() {
574 TimelineItemHandle::Local(send_handle) => {
575 if send_handle
576 .react(key.to_owned())
577 .await
578 .map_err(|err| Error::SendQueueError(err.into()))?
579 .is_some()
580 {
581 trace!("adding a reaction to a local echo");
582 return Ok(true);
583 }
584
585 warn!("couldn't toggle reaction for local echo");
586 return Ok(false);
587 }
588
589 TimelineItemHandle::Remote(event_id) => {
590 trace!("adding a reaction to a remote echo");
594 let annotation = Annotation::new(event_id.to_owned(), key.to_owned());
595 self.room_data_provider
596 .send(ReactionEventContent::from(annotation).into())
597 .await?;
598 return Ok(true);
599 }
600 }
601 };
602
603 trace!("removing a previous reaction");
604 match prev_status {
605 ReactionStatus::LocalToLocal(send_reaction_handle) => {
606 if let Some(handle) = send_reaction_handle {
607 if !handle.abort().await.map_err(|err| Error::SendQueueError(err.into()))? {
608 warn!("unexpectedly unable to abort sending of local reaction");
611 }
612 } else {
613 warn!("no send reaction handle (this should only happen in testing contexts)");
614 }
615 }
616
617 ReactionStatus::LocalToRemote(send_handle) => {
618 trace!("aborting send of the previous reaction that was a local echo");
621 if let Some(handle) = send_handle {
622 if !handle.abort().await.map_err(|err| Error::SendQueueError(err.into()))? {
623 warn!("unexpectedly unable to abort sending of local reaction");
626 }
627 } else {
628 warn!("no send handle (this should only happen in testing contexts)");
629 }
630 }
631
632 ReactionStatus::RemoteToRemote(event_id) => {
633 let Some(annotated_event_id) =
635 item.as_remote().map(|event_item| event_item.event_id.clone())
636 else {
637 warn!("remote reaction to remote event, but the associated item isn't remote");
638 return Ok(false);
639 };
640
641 let mut reactions = item.content().reactions().cloned().unwrap_or_default();
642 let reaction_info = reactions.remove_reaction(user_id, key);
643
644 if reaction_info.is_some() {
645 let new_item = item.with_reactions(reactions);
646 state.items.replace(item_pos, new_item);
647 } else {
648 warn!(
649 "reaction is missing on the item, not removing it locally, \
650 but sending redaction."
651 );
652 }
653
654 drop(state);
656
657 trace!("sending redact for a previous reaction");
658 if let Err(err) = self.room_data_provider.redact(&event_id, None, None).await {
659 if let Some(reaction_info) = reaction_info {
660 debug!("sending redact failed, adding the reaction back to the list");
661
662 let mut state = self.state.write().await;
663 if let Some((item_pos, item)) =
664 rfind_event_by_id(&state.items, &annotated_event_id)
665 {
666 let mut reactions =
668 item.content().reactions().cloned().unwrap_or_default();
669 reactions
670 .entry(key.to_owned())
671 .or_default()
672 .insert(user_id.to_owned(), reaction_info);
673 let new_item = item.with_reactions(reactions);
674 state.items.replace(item_pos, new_item);
675 } else {
676 warn!(
677 "couldn't find item to re-add reaction anymore; \
678 maybe it's been redacted?"
679 );
680 }
681 }
682
683 return Err(err);
684 }
685 }
686 }
687
688 Ok(false)
689 }
690
691 pub(super) async fn handle_remote_events_with_diffs(
693 &self,
694 diffs: Vec<VectorDiff<TimelineEvent>>,
695 origin: RemoteEventOrigin,
696 ) {
697 if diffs.is_empty() {
698 return;
699 }
700
701 let mut state = self.state.write().await;
702 state
703 .handle_remote_events_with_diffs(
704 diffs,
705 origin,
706 &self.room_data_provider,
707 &self.settings,
708 )
709 .await
710 }
711
712 pub(super) async fn handle_remote_aggregations(
714 &self,
715 diffs: Vec<VectorDiff<TimelineEvent>>,
716 origin: RemoteEventOrigin,
717 ) {
718 if diffs.is_empty() {
719 return;
720 }
721
722 let mut state = self.state.write().await;
723 state
724 .handle_remote_aggregations(diffs, origin, &self.room_data_provider, &self.settings)
725 .await
726 }
727
728 pub(super) async fn clear(&self) {
729 self.state.write().await.clear();
730 }
731
732 pub(super) async fn replace_with_initial_remote_events<Events>(
740 &self,
741 events: Events,
742 origin: RemoteEventOrigin,
743 ) where
744 Events: IntoIterator,
745 <Events as IntoIterator>::Item: Into<TimelineEvent>,
746 {
747 let mut state = self.state.write().await;
748
749 let track_read_markers = &self.settings.track_read_receipts;
750 if track_read_markers.is_enabled() {
751 state.populate_initial_user_receipt(&self.room_data_provider, ReceiptType::Read).await;
752 state
753 .populate_initial_user_receipt(&self.room_data_provider, ReceiptType::ReadPrivate)
754 .await;
755 }
756
757 let mut events = events.into_iter().peekable();
763 if !state.items.is_empty() || events.peek().is_some() {
764 state
765 .replace_with_remote_events(
766 events,
767 origin,
768 &self.room_data_provider,
769 &self.settings,
770 )
771 .await;
772 }
773
774 if track_read_markers.is_enabled() {
775 if let Some(fully_read_event_id) =
776 self.room_data_provider.load_fully_read_marker().await
777 {
778 state.handle_fully_read_marker(fully_read_event_id);
779 } else if let Some(latest_receipt_event_id) = state
780 .latest_user_read_receipt_timeline_event_id(self.room_data_provider.own_user_id())
781 {
782 debug!("no `m.fully_read` marker found, falling back to read receipt");
784 state.handle_fully_read_marker(latest_receipt_event_id);
785 }
786 }
787 }
788
789 pub(super) async fn handle_fully_read_marker(&self, fully_read_event_id: OwnedEventId) {
790 self.state.write().await.handle_fully_read_marker(fully_read_event_id);
791 }
792
793 pub(super) async fn handle_active_call_update(
794 &self,
795 maybe_active_call: Option<ActiveCallInfo>,
796 ) {
797 let mut state = self.state.write().await;
798 let mut txn = state.transaction();
799
800 txn.meta.active_call = maybe_active_call.clone();
802
803 if let Some(existing_event_id) = &txn.meta.active_rtc_notification_event_id {
804 let last_notification = rfind_event_by_id(&txn.items, existing_event_id);
806 if let Some((last_idx, last_notification)) = last_notification {
807 let updated_content = match last_notification.content() {
808 TimelineItemContent::RtcNotification {
809 call_intent,
810 declined_by,
811 active_call_info: _active_call_info,
812 } => Some(TimelineItemContent::RtcNotification {
813 call_intent: call_intent.to_owned(),
814 declined_by: declined_by.clone(),
815 active_call_info: maybe_active_call
816 .clone()
817 .map(|info| info.with_start_time(last_notification.timestamp.into())),
818 }),
819 _ => None,
820 };
821 if let Some(new_content) = updated_content {
822 let new_event_item = last_notification.inner.with_content(new_content);
823 let new_timeline_item =
824 TimelineItem::new(new_event_item, last_notification.internal_id.clone());
825 txn.items.replace(last_idx, new_timeline_item);
826 }
827
828 if maybe_active_call.is_none() {
829 txn.meta.active_rtc_notification_event_id = None;
831 }
832 }
833 }
834
835 txn.commit();
836 }
837
838 pub(super) async fn handle_ephemeral_events(
839 &self,
840 events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
841 ) {
842 if events.is_empty() {
844 return;
845 }
846 let mut state = self.state.write().await;
847 state.handle_ephemeral_events(events, &self.room_data_provider).await;
848 }
849
850 #[instrument(skip_all)]
852 pub(super) async fn handle_local_event(
853 &self,
854 txn_id: OwnedTransactionId,
855 content: AnyMessageLikeEventContent,
856 send_handle: Option<SendHandle>,
857 ) {
858 let sender = self.room_data_provider.own_user_id().to_owned();
859 let profile = self.room_data_provider.profile_from_user_id(&sender).await;
860
861 let date_divider_mode = self.settings.date_divider_mode.clone();
862
863 let mut state = self.state.write().await;
864 state
865 .handle_local_event(sender, profile, date_divider_mode, txn_id, send_handle, content)
866 .await;
867 }
868
869 #[instrument(skip(self))]
874 pub(super) async fn update_event_send_state(
875 &self,
876 txn_id: &TransactionId,
877 send_state: EventSendState,
878 ) {
879 let mut state = self.state.write().await;
880 let mut txn = state.transaction();
881
882 let new_event_id: Option<&EventId> =
883 as_variant!(&send_state, EventSendState::Sent { event_id } => event_id);
884
885 if rfind_event_item(&txn.items, |it| {
888 new_event_id.is_some() && it.event_id() == new_event_id && it.as_remote().is_some()
889 })
890 .is_some()
891 {
892 trace!("Remote echo received before send-event response");
894
895 let local_echo = rfind_event_item(&txn.items, |it| it.transaction_id() == Some(txn_id));
896
897 if let Some((idx, _)) = local_echo {
901 warn!("Message echo got duplicated, removing the local one");
902 txn.items.remove(idx);
903
904 let mut adjuster =
906 DateDividerAdjuster::new(self.settings.date_divider_mode.clone());
907 adjuster.run(&mut txn.items, &mut txn.meta);
908 }
909
910 txn.commit();
911 return;
912 }
913
914 let result = rfind_event_item(&txn.items, |it| {
916 it.transaction_id() == Some(txn_id)
917 || new_event_id.is_some()
918 && it.event_id() == new_event_id
919 && it.as_local().is_some()
920 });
921
922 let Some((idx, item)) = result else {
923 if let Some(new_event_id) = new_event_id {
928 if txn.meta.aggregations.mark_aggregation_as_sent(
929 txn_id.to_owned(),
930 new_event_id.to_owned(),
931 &mut txn.items,
932 &txn.meta.room_version_rules,
933 ) {
934 trace!("Aggregation marked as sent");
935 txn.commit();
936 return;
937 }
938
939 trace!("Sent aggregation was not found");
940 }
941
942 warn!("Timeline item not found, can't update send state");
943 return;
944 };
945
946 let Some(local_item) = item.as_local() else {
947 warn!("We looked for a local item, but it transitioned to remote.");
948 return;
949 };
950
951 if let EventSendState::Sent { event_id: existing_event_id } = &local_item.send_state {
954 error!(?existing_event_id, ?new_event_id, "Local echo already marked as sent");
955 }
956
957 if let Some(new_event_id) = new_event_id {
960 txn.meta.aggregations.mark_target_as_sent(txn_id.to_owned(), new_event_id.to_owned());
961 }
962
963 let new_item = item.with_inner_kind(local_item.with_send_state(send_state));
964 txn.items.replace(idx, new_item);
965
966 txn.commit();
967 }
968
969 pub(super) async fn discard_local_echo(&self, txn_id: &TransactionId) -> bool {
970 let mut state = self.state.write().await;
971
972 if let Some((idx, _)) =
973 rfind_event_item(&state.items, |it| it.transaction_id() == Some(txn_id))
974 {
975 let mut txn = state.transaction();
976
977 txn.items.remove(idx);
978
979 let mut adjuster = DateDividerAdjuster::new(self.settings.date_divider_mode.clone());
982 adjuster.run(&mut txn.items, &mut txn.meta);
983
984 txn.meta.update_read_marker(&mut txn.items);
985
986 txn.commit();
987
988 debug!("discarded local echo");
989 return true;
990 }
991
992 let mut txn = state.transaction();
995
996 let found_aggregation = match txn.meta.aggregations.try_remove_aggregation(
998 &TimelineEventItemId::TransactionId(txn_id.to_owned()),
999 &mut txn.items,
1000 ) {
1001 Ok(val) => val,
1002 Err(err) => {
1003 warn!("error when discarding local echo for an aggregation: {err}");
1004 true
1006 }
1007 };
1008
1009 if found_aggregation {
1010 txn.commit();
1011 }
1012
1013 found_aggregation
1014 }
1015
1016 pub(super) async fn replace_local_echo(
1017 &self,
1018 txn_id: &TransactionId,
1019 content: AnyMessageLikeEventContent,
1020 ) -> bool {
1021 let AnyMessageLikeEventContent::RoomMessage(content) = content else {
1022 warn!("Replacing a local echo for a non-RoomMessage-like event NYI");
1027 return false;
1028 };
1029
1030 let mut state = self.state.write().await;
1031 let mut txn = state.transaction();
1032
1033 let Some((idx, prev_item)) =
1034 rfind_event_item(&txn.items, |it| it.transaction_id() == Some(txn_id))
1035 else {
1036 debug!("Can't find local echo to replace");
1037 return false;
1038 };
1039
1040 let ti_kind = {
1043 let Some(prev_local_item) = prev_item.as_local() else {
1044 warn!("We looked for a local item, but it transitioned as remote??");
1045 return false;
1046 };
1047 let progress = as_variant!(&prev_local_item.send_state,
1049 EventSendState::NotSentYet { progress } => progress.clone())
1050 .flatten();
1051 prev_local_item.with_send_state(EventSendState::NotSentYet { progress })
1052 };
1053
1054 let new_item = TimelineItem::new(
1056 prev_item.with_kind(ti_kind).with_content(TimelineItemContent::message(
1057 content.msgtype,
1058 content.mentions,
1059 prev_item.content().reactions().cloned().unwrap_or_default(),
1060 prev_item.content().thread_root(),
1061 prev_item.content().in_reply_to(),
1062 prev_item.content().thread_summary(),
1063 )),
1064 prev_item.internal_id.to_owned(),
1065 );
1066
1067 txn.items.replace(idx, new_item);
1068
1069 txn.commit();
1073
1074 debug!("Replaced local echo");
1075 true
1076 }
1077
1078 pub(super) async fn compute_redecryption_candidates(
1079 &self,
1080 ) -> (BTreeSet<String>, BTreeSet<String>) {
1081 let state = self.state.read().await;
1082 compute_redecryption_candidates(&state.items)
1083 }
1084
1085 pub(super) async fn set_sender_profiles_pending(&self) {
1086 self.set_non_ready_sender_profiles(TimelineDetails::Pending).await;
1087 }
1088
1089 pub(super) async fn set_sender_profiles_error(&self, error: Arc<matrix_sdk::Error>) {
1090 self.set_non_ready_sender_profiles(TimelineDetails::Error(error)).await;
1091 }
1092
1093 async fn set_non_ready_sender_profiles(&self, profile_state: TimelineDetails<Profile>) {
1094 self.state.write().await.items.for_each(|mut entry| {
1095 let Some(event_item) = entry.as_event() else { return };
1096 if !matches!(event_item.sender_profile(), TimelineDetails::Ready(_)) {
1097 let new_item = entry.with_kind(TimelineItemKind::Event(
1098 event_item.with_sender_profile(profile_state.clone()),
1099 ));
1100 ObservableItemsEntry::replace(&mut entry, new_item);
1101 }
1102 });
1103 }
1104
1105 pub(super) async fn update_missing_sender_profiles(&self) {
1106 trace!("Updating missing sender profiles");
1107
1108 let mut state = self.state.write().await;
1109 let mut entries = state.items.entries();
1110 while let Some(mut entry) = entries.next() {
1111 let Some(event_item) = entry.as_event() else { continue };
1112 let event_id = event_item.event_id().map(debug);
1113 let transaction_id = event_item.transaction_id().map(debug);
1114
1115 if event_item.sender_profile().is_ready() {
1116 trace!(event_id, transaction_id, "Profile already set");
1117 continue;
1118 }
1119
1120 match self.room_data_provider.profile_from_user_id(event_item.sender()).await {
1121 Some(profile) => {
1122 trace!(event_id, transaction_id, "Adding profile");
1123 let updated_item =
1124 event_item.with_sender_profile(TimelineDetails::Ready(profile));
1125 let new_item = entry.with_kind(updated_item);
1126 ObservableItemsEntry::replace(&mut entry, new_item);
1127 }
1128 None => {
1129 if !event_item.sender_profile().is_unavailable() {
1130 trace!(event_id, transaction_id, "Marking profile unavailable");
1131 let updated_item =
1132 event_item.with_sender_profile(TimelineDetails::Unavailable);
1133 let new_item = entry.with_kind(updated_item);
1134 ObservableItemsEntry::replace(&mut entry, new_item);
1135 } else {
1136 debug!(event_id, transaction_id, "Profile already marked unavailable");
1137 }
1138 }
1139 }
1140 }
1141
1142 trace!("Done updating missing sender profiles");
1143 }
1144
1145 pub(super) async fn force_update_sender_profiles(&self, sender_ids: &BTreeSet<&UserId>) {
1147 trace!("Forcing update of sender profiles: {sender_ids:?}");
1148
1149 let mut state = self.state.write().await;
1150 let mut entries = state.items.entries();
1151 while let Some(mut entry) = entries.next() {
1152 let Some(event_item) = entry.as_event() else { continue };
1153 if !sender_ids.contains(event_item.sender()) {
1154 continue;
1155 }
1156
1157 let event_id = event_item.event_id().map(debug);
1158 let transaction_id = event_item.transaction_id().map(debug);
1159
1160 match self.room_data_provider.profile_from_user_id(event_item.sender()).await {
1161 Some(profile) => {
1162 if matches!(event_item.sender_profile(), TimelineDetails::Ready(old_profile) if *old_profile == profile)
1163 {
1164 debug!(event_id, transaction_id, "Profile already up-to-date");
1165 } else {
1166 trace!(event_id, transaction_id, "Updating profile");
1167 let updated_item =
1168 event_item.with_sender_profile(TimelineDetails::Ready(profile));
1169 let new_item = entry.with_kind(updated_item);
1170 ObservableItemsEntry::replace(&mut entry, new_item);
1171 }
1172 }
1173 None => {
1174 if !event_item.sender_profile().is_unavailable() {
1175 trace!(event_id, transaction_id, "Marking profile unavailable");
1176 let updated_item =
1177 event_item.with_sender_profile(TimelineDetails::Unavailable);
1178 let new_item = entry.with_kind(updated_item);
1179 ObservableItemsEntry::replace(&mut entry, new_item);
1180 } else {
1181 debug!(event_id, transaction_id, "Profile already marked unavailable");
1182 }
1183 }
1184 }
1185 }
1186
1187 trace!("Done forcing update of sender profiles");
1188 }
1189
1190 #[cfg(test)]
1191 pub(super) async fn handle_read_receipts(&self, receipt_event_content: ReceiptEventContent) {
1192 let own_user_id = self.room_data_provider.own_user_id();
1193 self.state.write().await.handle_read_receipts(receipt_event_content, own_user_id);
1194 }
1195
1196 pub(super) async fn latest_user_read_receipt(
1200 &self,
1201 user_id: &UserId,
1202 ) -> Option<(OwnedEventId, Receipt)> {
1203 let receipt_thread = self.focus.receipt_thread();
1204
1205 self.state
1206 .read()
1207 .await
1208 .latest_user_read_receipt(user_id, receipt_thread, &self.room_data_provider)
1209 .await
1210 }
1211
1212 pub(super) async fn latest_user_read_receipt_timeline_event_id(
1215 &self,
1216 user_id: &UserId,
1217 ) -> Option<OwnedEventId> {
1218 self.state.read().await.latest_user_read_receipt_timeline_event_id(user_id)
1219 }
1220
1221 pub async fn subscribe_own_user_read_receipts_changed(
1223 &self,
1224 ) -> impl Stream<Item = ()> + use<P> {
1225 self.state.read().await.meta.read_receipts.subscribe_own_user_read_receipts_changed()
1226 }
1227
1228 pub(crate) async fn handle_local_echo(&self, echo: LocalEcho) {
1230 match echo.content {
1231 LocalEchoContent::Event { serialized_event, send_handle, send_error } => {
1232 let content = match serialized_event.deserialize() {
1233 Ok(d) => d,
1234 Err(err) => {
1235 warn!("error deserializing local echo: {err}");
1236 return;
1237 }
1238 };
1239
1240 self.handle_local_event(echo.transaction_id.clone(), content, Some(send_handle))
1241 .await;
1242
1243 if let Some(send_error) = send_error {
1244 self.update_event_send_state(
1245 &echo.transaction_id,
1246 EventSendState::SendingFailed {
1247 error: Arc::new(matrix_sdk::Error::SendQueueWedgeError(Box::new(
1248 send_error,
1249 ))),
1250 is_recoverable: false,
1251 },
1252 )
1253 .await;
1254 }
1255 }
1256
1257 LocalEchoContent::React { key, send_handle, applies_to } => {
1258 self.handle_local_reaction(key, send_handle, applies_to).await;
1259 }
1260
1261 LocalEchoContent::Redaction { redacts, send_error, .. } => {
1262 self.handle_local_redaction(echo.transaction_id.clone(), redacts).await;
1263
1264 if let Some(send_error) = send_error {
1265 self.update_event_send_state(
1266 &echo.transaction_id,
1267 EventSendState::SendingFailed {
1268 error: Arc::new(matrix_sdk::Error::SendQueueWedgeError(Box::new(
1269 send_error,
1270 ))),
1271 is_recoverable: false,
1272 },
1273 )
1274 .await;
1275 }
1276 }
1277 }
1278 }
1279
1280 #[instrument(skip(self, send_handle))]
1282 async fn handle_local_reaction(
1283 &self,
1284 reaction_key: String,
1285 send_handle: SendReactionHandle,
1286 applies_to: OwnedTransactionId,
1287 ) {
1288 let mut state = self.state.write().await;
1289 let mut tr = state.transaction();
1290
1291 let target = TimelineEventItemId::TransactionId(applies_to);
1292
1293 let reaction_txn_id = send_handle.transaction_id().to_owned();
1294 let reaction_status = ReactionStatus::LocalToLocal(Some(send_handle));
1295 let aggregation = Aggregation::new(
1296 TimelineEventItemId::TransactionId(reaction_txn_id),
1297 AggregationKind::Reaction {
1298 key: reaction_key.clone(),
1299 sender: self.room_data_provider.own_user_id().to_owned(),
1300 timestamp: MilliSecondsSinceUnixEpoch::now(),
1301 reaction_status,
1302 },
1303 );
1304
1305 tr.meta.aggregations.add(target.clone(), aggregation.clone());
1306 find_item_and_apply_aggregation(
1307 &tr.meta.aggregations,
1308 &mut tr.items,
1309 &target,
1310 aggregation,
1311 &tr.meta.room_version_rules,
1312 );
1313
1314 tr.commit();
1315 }
1316
1317 pub(super) async fn handle_local_redaction(
1319 &self,
1320 txn_id: OwnedTransactionId,
1321 redacts: OwnedEventId,
1322 ) {
1323 let mut state = self.state.write().await;
1324 let mut tr = state.transaction();
1325
1326 let target = TimelineEventItemId::EventId(redacts);
1327
1328 let aggregation = Aggregation::new(
1329 TimelineEventItemId::TransactionId(txn_id),
1330 AggregationKind::Redaction { is_local: true },
1331 );
1332
1333 tr.meta.aggregations.add(target.clone(), aggregation.clone());
1334 find_item_and_apply_aggregation(
1335 &tr.meta.aggregations,
1336 &mut tr.items,
1337 &target,
1338 aggregation,
1339 &tr.meta.room_version_rules,
1340 );
1341
1342 tr.commit();
1343 }
1344
1345 pub(crate) async fn handle_room_send_queue_update(&self, update: RoomSendQueueUpdate) {
1347 match update {
1348 RoomSendQueueUpdate::NewLocalEvent(echo) => {
1349 self.handle_local_echo(echo).await;
1350 }
1351
1352 RoomSendQueueUpdate::CancelledLocalEvent { transaction_id } => {
1353 if !self.discard_local_echo(&transaction_id).await {
1354 warn!("couldn't find the local echo to discard");
1355 }
1356 }
1357
1358 RoomSendQueueUpdate::ReplacedLocalEvent { transaction_id, new_content } => {
1359 let content = match new_content.deserialize() {
1360 Ok(d) => d,
1361 Err(err) => {
1362 warn!("error deserializing local echo (upon edit): {err}");
1363 return;
1364 }
1365 };
1366
1367 if !self.replace_local_echo(&transaction_id, content).await {
1368 warn!("couldn't find the local echo to replace");
1369 }
1370 }
1371
1372 RoomSendQueueUpdate::SendError { transaction_id, error, is_recoverable } => {
1373 self.update_event_send_state(
1374 &transaction_id,
1375 EventSendState::SendingFailed { error, is_recoverable },
1376 )
1377 .await;
1378 }
1379
1380 RoomSendQueueUpdate::RetryEvent { transaction_id } => {
1381 self.update_event_send_state(
1382 &transaction_id,
1383 EventSendState::NotSentYet { progress: None },
1384 )
1385 .await;
1386 }
1387
1388 RoomSendQueueUpdate::SentEvent { transaction_id, event_id } => {
1389 self.update_event_send_state(&transaction_id, EventSendState::Sent { event_id })
1390 .await;
1391 }
1392
1393 RoomSendQueueUpdate::MediaUpload { related_to, index, progress, .. } => {
1394 self.update_event_send_state(
1395 &related_to,
1396 EventSendState::NotSentYet {
1397 progress: Some(MediaUploadProgress { index, progress }),
1398 },
1399 )
1400 .await;
1401 }
1402 }
1403 }
1404
1405 pub async fn insert_timeline_start_if_missing(&self) {
1408 let mut state = self.state.write().await;
1409 let mut txn = state.transaction();
1410 txn.items.push_timeline_start_if_missing(
1411 txn.meta.new_timeline_item(VirtualTimelineItem::TimelineStart),
1412 );
1413 txn.commit();
1414 }
1415
1416 pub(super) async fn make_replied_to(
1422 &self,
1423 event: TimelineEvent,
1424 ) -> Result<Option<EmbeddedEvent>, Error> {
1425 let state = self.state.read().await;
1426 EmbeddedEvent::try_from_timeline_event(event, &self.room_data_provider, &state.meta).await
1427 }
1428}
1429
1430impl TimelineController {
1431 pub(super) fn room(&self) -> &Room {
1432 &self.room_data_provider
1433 }
1434
1435 pub(super) async fn init_focus(&self) -> Result<InitFocusResult, Error> {
1440 match self.focus.deref() {
1441 TimelineFocusKind::Live { event_cache, .. } => {
1442 let events = event_cache.events().await?;
1444
1445 let has_events = !events.is_empty();
1446
1447 self.replace_with_initial_remote_events(events, RemoteEventOrigin::Cache).await;
1448
1449 match event_cache.pagination().status().get() {
1450 PaginationStatus::Idle { hit_timeline_start } => {
1451 if hit_timeline_start {
1452 self.insert_timeline_start_if_missing().await;
1455 }
1456 }
1457 PaginationStatus::Paginating => {}
1458 }
1459
1460 Ok(InitFocusResult { has_events, focus_task: None })
1461 }
1462
1463 TimelineFocusKind::Event {
1464 focused_event_id: event_id,
1465 thread_mode,
1466 thread_root: focus_thread_root,
1467 event_cache,
1468 ..
1469 } => {
1470 let (events, receiver) = event_cache.subscribe().await?;
1471
1472 let has_events = !events.is_empty();
1473
1474 if let Some(thread_root) = event_cache.thread_root().await? {
1477 focus_thread_root.get_or_init(|| thread_root);
1478 }
1479
1480 self.replace_with_initial_remote_events(events, RemoteEventOrigin::Pagination)
1481 .await;
1482
1483 let task = self
1484 .room_data_provider
1485 .client()
1486 .task_monitor()
1487 .spawn_infinite_task(
1488 "timeline::event_focused_cache_updates",
1489 event_focused_task(
1490 event_id.clone(),
1491 (*thread_mode).into(),
1492 event_cache.clone(),
1493 self.clone(),
1494 receiver,
1495 ),
1496 )
1497 .abort_on_drop();
1498
1499 Ok(InitFocusResult { has_events, focus_task: Some(task) })
1500 }
1501
1502 TimelineFocusKind::Thread { event_cache, .. } => {
1503 let (has_events, subscriber) = self.init_with_thread_root(event_cache).await?;
1504
1505 let room = &self.room_data_provider;
1506 let span = info_span!(
1507 parent: Span::none(),
1508 "thread_live_update_handler",
1509 room_id = ?room.room_id(),
1510 );
1511 span.follows_from(Span::current());
1512
1513 let task = room
1514 .client()
1515 .task_monitor()
1516 .spawn_infinite_task(
1517 "timeline::thread_event_cache_updates",
1518 thread_updates_task(subscriber, event_cache.clone(), self.clone())
1519 .instrument(span),
1520 )
1521 .abort_on_drop();
1522
1523 Ok(InitFocusResult { has_events, focus_task: Some(task) })
1524 }
1525
1526 TimelineFocusKind::PinnedEvents { event_cache } => {
1527 let (initial_events, pinned_events_recv) = event_cache.subscribe().await?;
1528
1529 let has_events = !initial_events.is_empty();
1530
1531 self.replace_with_initial_remote_events(
1532 initial_events,
1533 RemoteEventOrigin::Pagination,
1534 )
1535 .await;
1536
1537 let task = self
1538 .room_data_provider
1539 .client()
1540 .task_monitor()
1541 .spawn_infinite_task(
1542 "timeline::pinned_events_cache_updates",
1543 pinned_events_task(event_cache.clone(), self.clone(), pinned_events_recv),
1544 )
1545 .abort_on_drop();
1546
1547 Ok(InitFocusResult { has_events, focus_task: Some(task) })
1548 }
1549 }
1550 }
1551
1552 pub(super) async fn init_with_thread_root(
1559 &self,
1560 event_cache: &ThreadEventCache,
1561 ) -> Result<(bool, EventCacheSubscriber<TimelineVectorDiffs>), Error> {
1562 let (events, subscriber) = event_cache.subscribe().await?;
1563 let has_events = !events.is_empty();
1564
1565 let mut related_events = Vector::new();
1569 for event_id in events.iter().filter_map(|event| event.event_id()) {
1570 if let Some((_original, related)) =
1571 event_cache.find_event_with_relations(event_id, None).await?
1572 {
1573 related_events.extend(related);
1574 }
1575 }
1576
1577 self.replace_with_initial_remote_events(events, RemoteEventOrigin::Cache).await;
1578
1579 if !related_events.is_empty() {
1581 self.handle_remote_aggregations(
1582 vec![VectorDiff::Append { values: related_events }],
1583 RemoteEventOrigin::Cache,
1584 )
1585 .await;
1586 }
1587
1588 Ok((has_events, subscriber))
1589 }
1590
1591 #[instrument(skip(self))]
1594 pub(super) async fn fetch_in_reply_to_details(&self, event_id: &EventId) -> Result<(), Error> {
1595 let state_guard = self.state.write().await;
1596 let (index, item) = rfind_event_by_id(&state_guard.items, event_id)
1597 .ok_or(Error::EventNotInTimeline(TimelineEventItemId::EventId(event_id.to_owned())))?;
1598 let remote_item = item
1599 .as_remote()
1600 .ok_or(Error::EventNotInTimeline(TimelineEventItemId::EventId(event_id.to_owned())))?
1601 .clone();
1602
1603 let TimelineItemContent::MsgLike(msglike) = item.content().clone() else {
1604 debug!("Event is not a message");
1605 return Ok(());
1606 };
1607 let Some(in_reply_to) = msglike.in_reply_to.clone() else {
1608 debug!("Event is not a reply");
1609 return Ok(());
1610 };
1611 if let TimelineDetails::Pending = &in_reply_to.event {
1612 debug!("Replied-to event is already being fetched");
1613 return Ok(());
1614 }
1615 if let TimelineDetails::Ready(_) = &in_reply_to.event {
1616 debug!("Replied-to event has already been fetched");
1617 return Ok(());
1618 }
1619
1620 let internal_id = item.internal_id.to_owned();
1621 let item = item.clone();
1622 let event = fetch_replied_to_event(
1623 state_guard,
1624 &self.state,
1625 index,
1626 &item,
1627 internal_id,
1628 &msglike,
1629 &in_reply_to.event_id,
1630 self.room(),
1631 )
1632 .await?;
1633
1634 let mut state = self.state.write().await;
1637 let (index, item) = rfind_event_by_id(&state.items, &remote_item.event_id)
1638 .ok_or(Error::EventNotInTimeline(TimelineEventItemId::EventId(event_id.to_owned())))?;
1639
1640 let TimelineItemContent::MsgLike(MsgLikeContent {
1643 kind: MsgLikeKind::Message(message),
1644 reactions,
1645 thread_root,
1646 in_reply_to,
1647 thread_summary,
1648 }) = item.content().clone()
1649 else {
1650 info!("Event is no longer a message (redacted?)");
1651 return Ok(());
1652 };
1653 let Some(in_reply_to) = in_reply_to else {
1654 warn!("Event no longer has a reply (bug?)");
1655 return Ok(());
1656 };
1657
1658 trace!("Updating in-reply-to details");
1661 let internal_id = item.internal_id.to_owned();
1662 let mut item = item.clone();
1663 item.set_content(TimelineItemContent::MsgLike(MsgLikeContent {
1664 kind: MsgLikeKind::Message(message),
1665 reactions,
1666 thread_root,
1667 in_reply_to: Some(InReplyToDetails { event_id: in_reply_to.event_id, event }),
1668 thread_summary,
1669 }));
1670 state.items.replace(index, TimelineItem::new(item, internal_id));
1671
1672 Ok(())
1673 }
1674
1675 pub(super) fn infer_thread_for_read_receipt(
1681 &self,
1682 receipt_type: &SendReceiptType,
1683 ) -> ReceiptThread {
1684 if matches!(receipt_type, SendReceiptType::FullyRead) {
1685 ReceiptThread::Unthreaded
1686 } else {
1687 self.focus.receipt_thread()
1688 }
1689 }
1690
1691 pub(super) async fn should_send_receipt(
1695 &self,
1696 receipt_type: &SendReceiptType,
1697 receipt_thread: &ReceiptThread,
1698 event_id: &EventId,
1699 ) -> bool {
1700 let own_user_id = self.room().own_user_id();
1701 let state = self.state.read().await;
1702 let room = self.room();
1703
1704 match receipt_type {
1705 SendReceiptType::Read => {
1706 if let Some((old_pub_read, _)) = state
1707 .meta
1708 .user_receipt(
1709 own_user_id,
1710 ReceiptType::Read,
1711 receipt_thread.clone(),
1712 room,
1713 state.items.all_remote_events(),
1714 )
1715 .await
1716 {
1717 trace!(%old_pub_read, "found a previous public receipt");
1718 if let Some(relative_pos) = TimelineMetadata::compare_events_positions(
1719 &old_pub_read,
1720 event_id,
1721 state.items.all_remote_events(),
1722 ) {
1723 trace!(
1724 "event referred to new receipt is {relative_pos:?} the previous receipt"
1725 );
1726 return relative_pos == RelativePosition::After;
1727 }
1728 }
1729 }
1730
1731 SendReceiptType::ReadPrivate => {
1734 if let Some((old_priv_read, _)) =
1735 state.latest_user_read_receipt(own_user_id, receipt_thread.clone(), room).await
1736 {
1737 trace!(%old_priv_read, "found a previous private receipt");
1738 if let Some(relative_pos) = TimelineMetadata::compare_events_positions(
1739 &old_priv_read,
1740 event_id,
1741 state.items.all_remote_events(),
1742 ) {
1743 trace!(
1744 "event referred to new receipt is {relative_pos:?} the previous receipt"
1745 );
1746 return relative_pos == RelativePosition::After;
1747 }
1748 }
1749 }
1750
1751 SendReceiptType::FullyRead => {
1752 if let Some(prev_event_id) = self.room_data_provider.load_fully_read_marker().await
1753 && let Some(relative_pos) = TimelineMetadata::compare_events_positions(
1754 &prev_event_id,
1755 event_id,
1756 state.items.all_remote_events(),
1757 )
1758 {
1759 return relative_pos == RelativePosition::After;
1760 }
1761 }
1762
1763 _ => {}
1764 }
1765
1766 true
1768 }
1769
1770 pub(crate) async fn latest_event_id(&self) -> Option<OwnedEventId> {
1773 let state = self.state.read().await;
1774 let filter_out_thread_events = match self.focus() {
1775 TimelineFocusKind::Thread { .. } => false,
1776 TimelineFocusKind::Live { hide_threaded_events, .. } => *hide_threaded_events,
1777 TimelineFocusKind::Event { .. } => {
1778 false
1780 }
1781 TimelineFocusKind::PinnedEvents { .. } => true,
1782 };
1783
1784 state
1785 .items
1786 .all_remote_events()
1787 .iter()
1788 .rev()
1789 .filter_map(|event_meta| {
1790 if !filter_out_thread_events {
1791 Some(event_meta.event_id.clone())
1793 } else if event_meta.thread_root_id.is_none() {
1794 if let Some(TimelineEventItemId::EventId(target_event_id)) =
1800 state.meta.aggregations.is_aggregation_of(&TimelineEventItemId::EventId(
1801 event_meta.event_id.clone(),
1802 ))
1803 && let Some(target_meta) =
1804 state.items.all_remote_events().get_by_event_id(target_event_id)
1805 && target_meta.thread_root_id.is_some()
1806 {
1807 None
1809 } else {
1810 Some(event_meta.event_id.clone())
1813 }
1814 } else {
1815 None
1818 }
1819 })
1820 .next()
1821 }
1822
1823 #[instrument(skip(self), fields(room_id = ?self.room().room_id()))]
1824 pub(super) async fn retry_event_decryption(&self, session_ids: Option<BTreeSet<String>>) {
1825 let (utds, decrypted) = self.compute_redecryption_candidates().await;
1826
1827 let request = DecryptionRetryRequest {
1828 room_id: self.room().room_id().to_owned(),
1829 utd_session_ids: utds,
1830 refresh_info_session_ids: decrypted,
1831 };
1832
1833 self.room().client().event_cache().request_decryption(request);
1834 }
1835
1836 pub(super) async fn map_pagination_status(&self, status: PaginationStatus) -> PaginationStatus {
1844 match status {
1845 PaginationStatus::Idle { hit_timeline_start } => {
1846 if hit_timeline_start {
1847 let state = self.state.read().await;
1848 if state.meta.subscriber_skip_count.get() > 0 {
1852 return PaginationStatus::Idle { hit_timeline_start: false };
1853 }
1854 }
1855 }
1856 PaginationStatus::Paginating => {}
1857 }
1858
1859 status
1861 }
1862}
1863
1864impl<P: RoomDataProvider> TimelineController<P> {
1865 pub(super) fn focus(&self) -> &TimelineFocusKind {
1867 &self.focus
1868 }
1869}
1870
1871#[allow(clippy::too_many_arguments)]
1872async fn fetch_replied_to_event<P: RoomDataProvider>(
1873 mut state_guard: RwLockWriteGuard<'_, TimelineState<P>>,
1874 state_lock: &RwLock<TimelineState<P>>,
1875 index: usize,
1876 item: &EventTimelineItem,
1877 internal_id: TimelineUniqueId,
1878 msglike: &MsgLikeContent,
1879 in_reply_to: &EventId,
1880 room: &Room,
1881) -> Result<TimelineDetails<Box<EmbeddedEvent>>, Error> {
1882 if let Some((_, item)) = rfind_event_by_id(&state_guard.items, in_reply_to) {
1883 let details = TimelineDetails::Ready(Box::new(EmbeddedEvent::from_timeline_item(&item)));
1884 trace!("Found replied-to event locally");
1885 return Ok(details);
1886 }
1887
1888 trace!("Setting in-reply-to details to pending");
1891 let in_reply_to_details =
1892 InReplyToDetails { event_id: in_reply_to.to_owned(), event: TimelineDetails::Pending };
1893
1894 let event_item = item
1895 .with_content(TimelineItemContent::MsgLike(msglike.with_in_reply_to(in_reply_to_details)));
1896
1897 let new_timeline_item = TimelineItem::new(event_item, internal_id);
1898 state_guard.items.replace(index, new_timeline_item);
1899
1900 drop(state_guard);
1902
1903 trace!("Fetching replied-to event");
1904 let res = match room.load_or_fetch_event(in_reply_to, None).await {
1905 Ok(timeline_event) => {
1906 let state = state_lock.read().await;
1907
1908 let replied_to_item =
1909 EmbeddedEvent::try_from_timeline_event(timeline_event, room, &state.meta).await?;
1910
1911 if let Some(item) = replied_to_item {
1912 TimelineDetails::Ready(Box::new(item))
1913 } else {
1914 return Err(Error::UnsupportedEvent);
1916 }
1917 }
1918
1919 Err(e) => TimelineDetails::Error(Arc::new(e)),
1920 };
1921
1922 Ok(res)
1923}