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