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::{future::Future, 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, TimelineItem,
32        date_dividers::DateDividerAdjuster,
33        event_handler::{
34            Flow, TimelineAction, TimelineEventContext, TimelineEventHandler, TimelineItemPosition,
35        },
36        event_item::RemoteEventOrigin,
37        traits::RoomDataProvider,
38    },
39    DateDividerMode, TimelineMetadata, TimelineSettings, TimelineStateTransaction,
40    observable_items::ObservableItems,
41};
42use crate::{timeline::controller::TimelineFocusKind, unable_to_decrypt_hook::UtdHookManager};
43
44#[derive(Debug)]
45pub(in crate::timeline) struct TimelineState<P: RoomDataProvider> {
46    pub items: ObservableItems,
47    pub meta: TimelineMetadata,
48
49    /// The kind of focus of this timeline.
50    pub(super) focus: Arc<TimelineFocusKind<P>>,
51}
52
53impl<P: RoomDataProvider> TimelineState<P> {
54    pub(super) fn new(
55        focus: Arc<TimelineFocusKind<P>>,
56        own_user_id: OwnedUserId,
57        room_version_rules: RoomVersionRules,
58        internal_id_prefix: Option<String>,
59        unable_to_decrypt_hook: Option<Arc<UtdHookManager>>,
60        is_room_encrypted: bool,
61    ) -> Self {
62        Self {
63            items: ObservableItems::new(),
64            meta: TimelineMetadata::new(
65                own_user_id,
66                room_version_rules,
67                internal_id_prefix,
68                unable_to_decrypt_hook,
69                is_room_encrypted,
70            ),
71            focus,
72        }
73    }
74
75    /// Handle updates on events as [`VectorDiff`]s.
76    pub(super) async fn handle_remote_events_with_diffs(
77        &mut self,
78        diffs: Vec<VectorDiff<TimelineEvent>>,
79        origin: RemoteEventOrigin,
80        room_data: &P,
81        settings: &TimelineSettings,
82    ) {
83        if diffs.is_empty() {
84            return;
85        }
86
87        let mut transaction = self.transaction();
88        transaction.handle_remote_events_with_diffs(diffs, origin, room_data, settings).await;
89        transaction.commit();
90    }
91
92    /// Handle remote aggregations on events as [`VectorDiff`]s.
93    pub(super) async fn handle_remote_aggregations(
94        &mut self,
95        diffs: Vec<VectorDiff<TimelineEvent>>,
96        origin: RemoteEventOrigin,
97        room_data: &P,
98        settings: &TimelineSettings,
99    ) {
100        if diffs.is_empty() {
101            return;
102        }
103
104        let mut transaction = self.transaction();
105        transaction.handle_remote_aggregations(diffs, origin, room_data, settings).await;
106        transaction.commit();
107    }
108
109    /// Marks the given event as fully read, using the read marker received from
110    /// sync.
111    pub(super) fn handle_fully_read_marker(&mut self, fully_read_event_id: OwnedEventId) {
112        let mut txn = self.transaction();
113        txn.set_fully_read_event(fully_read_event_id);
114        txn.commit();
115    }
116
117    #[instrument(skip_all)]
118    pub(super) async fn handle_ephemeral_events(
119        &mut self,
120        events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
121        room_data_provider: &P,
122    ) {
123        if events.is_empty() {
124            return;
125        }
126
127        let mut txn = self.transaction();
128
129        trace!("Handling ephemeral room events");
130        let own_user_id = room_data_provider.own_user_id();
131        for raw_event in events {
132            match raw_event.deserialize() {
133                Ok(AnySyncEphemeralRoomEvent::Receipt(ev)) => {
134                    txn.handle_explicit_read_receipts(ev.content, own_user_id);
135                }
136                Ok(_) => {}
137                Err(e) => {
138                    let event_type = raw_event.get_field::<String>("type").ok().flatten();
139                    warn!(event_type, "Failed to deserialize ephemeral event: {e}");
140                }
141            }
142        }
143
144        txn.commit();
145    }
146
147    /// Adds a local echo (for an event) to the timeline.
148    #[allow(clippy::too_many_arguments)]
149    #[instrument(skip_all)]
150    pub(super) async fn handle_local_event(
151        &mut self,
152        own_user_id: OwnedUserId,
153        own_profile: Option<Profile>,
154        date_divider_mode: DateDividerMode,
155        txn_id: OwnedTransactionId,
156        send_handle: Option<SendHandle>,
157        content: AnyMessageLikeEventContent,
158    ) {
159        let mut txn = self.transaction();
160
161        let mut date_divider_adjuster = DateDividerAdjuster::new(date_divider_mode);
162
163        let is_thread_focus = matches!(txn.focus, TimelineFocusKind::Thread { .. });
164        let (in_reply_to, thread_root) =
165            txn.meta.process_content_relations(&content, None, &txn.items, is_thread_focus);
166
167        // TODO merge with other should_add, one way or another?
168        let should_add_new_items = match &txn.focus {
169            TimelineFocusKind::Live { hide_threaded_events } => {
170                thread_root.is_none() || !hide_threaded_events
171            }
172            TimelineFocusKind::Thread { root_event_id, .. } => {
173                thread_root.as_ref().is_some_and(|r| r == root_event_id)
174            }
175            TimelineFocusKind::Event { .. } | TimelineFocusKind::PinnedEvents { .. } => {
176                // Don't add new items to these timelines; aggregations are added independently
177                // of the `should_add_new_items` value.
178                false
179            }
180        };
181
182        let ctx = TimelineEventContext {
183            sender: own_user_id,
184            sender_profile: own_profile,
185            timestamp: MilliSecondsSinceUnixEpoch::now(),
186            read_receipts: Default::default(),
187            // An event sent by ourselves is never matched against push rules.
188            is_highlighted: false,
189            flow: Flow::Local { txn_id, send_handle },
190            should_add_new_items,
191        };
192
193        if let Some(timeline_action) =
194            TimelineAction::from_content(content, in_reply_to, thread_root, None)
195        {
196            TimelineEventHandler::new(&mut txn, ctx)
197                .handle_event(&mut date_divider_adjuster, timeline_action)
198                .await;
199            txn.adjust_date_dividers(date_divider_adjuster);
200        }
201
202        txn.commit();
203    }
204
205    pub(super) async fn retry_event_decryption<Fut>(
206        &mut self,
207        retry_one: impl Fn(Arc<TimelineItem>) -> Fut,
208        retry_indices: Vec<usize>,
209        room_data_provider: &P,
210        settings: &TimelineSettings,
211    ) where
212        Fut: Future<Output = Option<TimelineEvent>>,
213    {
214        let mut txn = self.transaction();
215
216        let mut date_divider_adjuster =
217            DateDividerAdjuster::new(settings.date_divider_mode.clone());
218
219        // Loop through all the indices, in order so we don't decrypt edits
220        // before the event being edited, if both were UTD. Keep track of
221        // index change as UTDs are removed instead of updated.
222        let mut offset = 0;
223        for idx in retry_indices {
224            let idx = idx - offset;
225            let Some(event) = retry_one(txn.items[idx].clone()).await else {
226                continue;
227            };
228
229            let removed_item = txn
230                .handle_remote_event(
231                    event,
232                    TimelineItemPosition::UpdateAt { timeline_item_index: idx },
233                    room_data_provider,
234                    settings,
235                    &mut date_divider_adjuster,
236                )
237                .await;
238
239            // If the UTD was removed rather than updated, offset all
240            // subsequent loop iterations.
241            if removed_item {
242                offset += 1;
243            }
244        }
245
246        txn.adjust_date_dividers(date_divider_adjuster);
247
248        txn.commit();
249    }
250
251    #[cfg(test)]
252    pub(super) fn handle_read_receipts(
253        &mut self,
254        receipt_event_content: ReceiptEventContent,
255        own_user_id: &ruma::UserId,
256    ) {
257        let mut txn = self.transaction();
258        txn.handle_explicit_read_receipts(receipt_event_content, own_user_id);
259        txn.commit();
260    }
261
262    pub(super) fn clear(&mut self) {
263        let mut txn = self.transaction();
264        txn.clear();
265        txn.commit();
266    }
267
268    /// Replaces the existing events in the timeline with the given remote ones.
269    ///
270    /// Note: when the `position` is [`TimelineEnd::Front`], prepended events
271    /// should be ordered in *reverse* topological order, that is, `events[0]`
272    /// is the most recent.
273    pub(super) async fn replace_with_remote_events<Events>(
274        &mut self,
275        events: Events,
276        origin: RemoteEventOrigin,
277        room_data_provider: &P,
278        settings: &TimelineSettings,
279    ) where
280        Events: IntoIterator,
281        Events::Item: Into<TimelineEvent>,
282    {
283        let mut txn = self.transaction();
284        txn.clear();
285        txn.handle_remote_events_with_diffs(
286            vec![VectorDiff::Append { values: events.into_iter().map(Into::into).collect() }],
287            origin,
288            room_data_provider,
289            settings,
290        )
291        .await;
292        txn.commit();
293    }
294
295    pub(super) fn mark_all_events_as_encrypted(&mut self) {
296        // When this transaction finishes, all items in the timeline will be emitted
297        // again with the updated encryption value.
298        let mut txn = self.transaction();
299        txn.mark_all_events_as_encrypted();
300        txn.commit();
301    }
302
303    pub(super) fn transaction(&mut self) -> TimelineStateTransaction<'_, P> {
304        TimelineStateTransaction::new(&mut self.items, &mut self.meta, &*self.focus)
305    }
306}