Skip to main content

matrix_sdk_ui/timeline/controller/
state.rs

1// Copyright 2023 The Matrix.org Foundation C.I.C.
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::sync::Arc;
16
17use eyeball_im::VectorDiff;
18use matrix_sdk::{deserialized_responses::TimelineEvent, send_queue::SendHandle};
19#[cfg(test)]
20use ruma::events::receipt::ReceiptEventContent;
21use ruma::{
22    MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
23    events::{AnyMessageLikeEventContent, AnySyncEphemeralRoomEvent},
24    room_version_rules::RoomVersionRules,
25    serde::Raw,
26};
27use tracing::{instrument, trace, warn};
28
29use super::{
30    super::{
31        Profile,
32        date_dividers::DateDividerAdjuster,
33        event_handler::{Flow, TimelineAction, TimelineEventContext, TimelineEventHandler},
34        event_item::RemoteEventOrigin,
35        traits::RoomDataProvider,
36    },
37    DateDividerMode, TimelineMetadata, TimelineSettings, TimelineStateTransaction,
38    observable_items::ObservableItems,
39};
40use crate::{timeline::controller::TimelineFocusKind, unable_to_decrypt_hook::UtdHookManager};
41
42#[derive(Debug)]
43pub(in crate::timeline) struct TimelineState<P: RoomDataProvider> {
44    pub items: ObservableItems,
45    pub meta: TimelineMetadata,
46
47    /// The kind of focus of this timeline.
48    pub(super) focus: Arc<TimelineFocusKind>,
49
50    /// Phantom data for the room data provider.
51    _phantom: std::marker::PhantomData<P>,
52}
53
54impl<P: RoomDataProvider> TimelineState<P> {
55    pub(super) fn new(
56        focus: Arc<TimelineFocusKind>,
57        own_user_id: OwnedUserId,
58        room_version_rules: RoomVersionRules,
59        internal_id_prefix: Option<String>,
60        unable_to_decrypt_hook: Option<Arc<UtdHookManager>>,
61        is_room_encrypted: bool,
62    ) -> Self {
63        Self {
64            items: ObservableItems::new(),
65            meta: TimelineMetadata::new(
66                own_user_id,
67                room_version_rules,
68                internal_id_prefix,
69                unable_to_decrypt_hook,
70                is_room_encrypted,
71            ),
72            focus,
73            _phantom: std::marker::PhantomData,
74        }
75    }
76
77    /// Handle updates on events as [`VectorDiff`]s.
78    pub(super) async fn handle_remote_events_with_diffs(
79        &mut self,
80        diffs: Vec<VectorDiff<TimelineEvent>>,
81        origin: RemoteEventOrigin,
82        room_data: &P,
83        settings: &TimelineSettings,
84    ) {
85        if diffs.is_empty() {
86            return;
87        }
88
89        let mut transaction = self.transaction();
90        transaction.handle_remote_events_with_diffs(diffs, origin, room_data, settings).await;
91        transaction.commit();
92    }
93
94    /// Handle remote aggregations on events as [`VectorDiff`]s.
95    pub(super) async fn handle_remote_aggregations(
96        &mut self,
97        diffs: Vec<VectorDiff<TimelineEvent>>,
98        origin: RemoteEventOrigin,
99        room_data: &P,
100        settings: &TimelineSettings,
101    ) {
102        if diffs.is_empty() {
103            return;
104        }
105
106        let mut transaction = self.transaction();
107        transaction.handle_remote_aggregations(diffs, origin, room_data, settings).await;
108        transaction.commit();
109    }
110
111    /// Marks the given event as fully read, using the read marker received from
112    /// sync.
113    pub(super) fn handle_fully_read_marker(&mut self, fully_read_event_id: OwnedEventId) {
114        let mut txn = self.transaction();
115        txn.set_fully_read_event(fully_read_event_id);
116        txn.commit();
117    }
118
119    #[instrument(skip_all)]
120    pub(super) async fn handle_ephemeral_events(
121        &mut self,
122        events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
123        room_data_provider: &P,
124    ) {
125        if events.is_empty() {
126            return;
127        }
128
129        let mut txn = self.transaction();
130
131        trace!("Handling ephemeral room events");
132        let own_user_id = room_data_provider.own_user_id();
133        for raw_event in events {
134            match raw_event.deserialize() {
135                Ok(AnySyncEphemeralRoomEvent::Receipt(ev)) => {
136                    txn.handle_explicit_read_receipts(ev.content, own_user_id);
137                }
138                Ok(_) => {}
139                Err(e) => {
140                    let event_type = raw_event.get_field::<String>("type").ok().flatten();
141                    warn!(event_type, "Failed to deserialize ephemeral event: {e}");
142                }
143            }
144        }
145
146        txn.commit();
147    }
148
149    /// Adds a local echo (for an event) to the timeline.
150    #[allow(clippy::too_many_arguments)]
151    #[instrument(skip_all)]
152    pub(super) async fn handle_local_event(
153        &mut self,
154        own_user_id: OwnedUserId,
155        own_profile: Option<Profile>,
156        date_divider_mode: DateDividerMode,
157        txn_id: OwnedTransactionId,
158        send_handle: Option<SendHandle>,
159        content: AnyMessageLikeEventContent,
160    ) {
161        let mut txn = self.transaction();
162
163        let mut date_divider_adjuster = DateDividerAdjuster::new(date_divider_mode);
164
165        let is_thread_focus = txn.focus.is_thread();
166        let (in_reply_to, thread_root) =
167            txn.meta.process_content_relations(&content, None, &txn.items, is_thread_focus);
168
169        // TODO merge with other should_add, one way or another?
170        let should_add_new_items = match &txn.focus {
171            TimelineFocusKind::Live { hide_threaded_events } => {
172                thread_root.is_none() || !hide_threaded_events
173            }
174            TimelineFocusKind::Thread { root_event_id, .. } => {
175                thread_root.as_ref().is_some_and(|r| r == root_event_id)
176            }
177            TimelineFocusKind::Event { .. } | TimelineFocusKind::PinnedEvents => {
178                // Don't add new items to these timelines; aggregations are added independently
179                // of the `should_add_new_items` value.
180                false
181            }
182        };
183
184        let ctx = TimelineEventContext {
185            sender: own_user_id,
186            sender_profile: own_profile,
187            forwarder: None,
188            forwarder_profile: None,
189            timestamp: MilliSecondsSinceUnixEpoch::now(),
190            read_receipts: Default::default(),
191            // An event sent by ourselves is never matched against push rules.
192            is_highlighted: false,
193            flow: Flow::Local { txn_id, send_handle },
194            should_add_new_items,
195        };
196
197        let timeline_action = TimelineAction::from_content(content, in_reply_to, thread_root, None);
198        TimelineEventHandler::new(&mut txn, ctx)
199            .handle_event(&mut date_divider_adjuster, timeline_action, None)
200            .await;
201        txn.adjust_date_dividers(date_divider_adjuster);
202
203        txn.commit();
204    }
205
206    #[cfg(test)]
207    pub(super) fn handle_read_receipts(
208        &mut self,
209        receipt_event_content: ReceiptEventContent,
210        own_user_id: &ruma::UserId,
211    ) {
212        let mut txn = self.transaction();
213        txn.handle_explicit_read_receipts(receipt_event_content, own_user_id);
214        txn.commit();
215    }
216
217    pub(super) fn clear(&mut self) {
218        let mut txn = self.transaction();
219        txn.clear();
220        txn.commit();
221    }
222
223    /// Replaces the existing events in the timeline with the given remote ones.
224    ///
225    /// Note: when the `position` is [`TimelineEnd::Front`], prepended events
226    /// should be ordered in *reverse* topological order, that is, `events[0]`
227    /// is the most recent.
228    pub(super) async fn replace_with_remote_events<Events>(
229        &mut self,
230        events: Events,
231        origin: RemoteEventOrigin,
232        room_data_provider: &P,
233        settings: &TimelineSettings,
234    ) where
235        Events: IntoIterator,
236        Events::Item: Into<TimelineEvent>,
237    {
238        let mut txn = self.transaction();
239        txn.clear();
240        txn.handle_remote_events_with_diffs(
241            vec![VectorDiff::Append { values: events.into_iter().map(Into::into).collect() }],
242            origin,
243            room_data_provider,
244            settings,
245        )
246        .await;
247        txn.commit();
248    }
249
250    pub(super) fn mark_all_events_as_encrypted(&mut self) {
251        // When this transaction finishes, all items in the timeline will be emitted
252        // again with the updated encryption value.
253        let mut txn = self.transaction();
254        txn.mark_all_events_as_encrypted();
255        txn.commit();
256    }
257
258    pub(super) fn transaction(&mut self) -> TimelineStateTransaction<'_, P> {
259        TimelineStateTransaction::new(&mut self.items, &mut self.meta, &self.focus)
260    }
261}