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::{
23        poll::unstable_start::NewUnstablePollStartEventContentWithoutRelation,
24        relation::Replacement, room::message::RoomMessageEventContentWithoutRelation,
25        AnySyncEphemeralRoomEvent, AnySyncTimelineEvent,
26    },
27    serde::Raw,
28    EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
29    RoomVersionId, UserId,
30};
31use tracing::{instrument, trace, warn};
32
33use super::{
34    super::{
35        date_dividers::DateDividerAdjuster,
36        event_handler::{
37            Flow, TimelineEventContext, TimelineEventHandler, TimelineEventKind,
38            TimelineItemPosition,
39        },
40        event_item::RemoteEventOrigin,
41        traits::RoomDataProvider,
42        Profile, TimelineItem,
43    },
44    metadata::EventMeta,
45    observable_items::ObservableItems,
46    DateDividerMode, TimelineFocusKind, TimelineMetadata, TimelineSettings,
47    TimelineStateTransaction,
48};
49use crate::unable_to_decrypt_hook::UtdHookManager;
50
51#[derive(Debug)]
52pub(in crate::timeline) struct TimelineState {
53    pub items: ObservableItems,
54    pub meta: TimelineMetadata,
55
56    /// The kind of focus of this timeline.
57    pub timeline_focus: TimelineFocusKind,
58}
59
60impl TimelineState {
61    pub(super) fn new(
62        timeline_focus: TimelineFocusKind,
63        own_user_id: OwnedUserId,
64        room_version: RoomVersionId,
65        internal_id_prefix: Option<String>,
66        unable_to_decrypt_hook: Option<Arc<UtdHookManager>>,
67        is_room_encrypted: bool,
68    ) -> Self {
69        Self {
70            items: ObservableItems::new(),
71            meta: TimelineMetadata::new(
72                own_user_id,
73                room_version,
74                internal_id_prefix,
75                unable_to_decrypt_hook,
76                is_room_encrypted,
77            ),
78            timeline_focus,
79        }
80    }
81
82    /// Handle updates on events as [`VectorDiff`]s.
83    pub(super) async fn handle_remote_events_with_diffs<RoomData>(
84        &mut self,
85        diffs: Vec<VectorDiff<TimelineEvent>>,
86        origin: RemoteEventOrigin,
87        room_data: &RoomData,
88        settings: &TimelineSettings,
89    ) where
90        RoomData: RoomDataProvider,
91    {
92        if diffs.is_empty() {
93            return;
94        }
95
96        let mut transaction = self.transaction();
97        transaction.handle_remote_events_with_diffs(diffs, origin, room_data, settings).await;
98        transaction.commit();
99    }
100
101    /// Marks the given event as fully read, using the read marker received from
102    /// sync.
103    pub(super) fn handle_fully_read_marker(&mut self, fully_read_event_id: OwnedEventId) {
104        let mut txn = self.transaction();
105        txn.set_fully_read_event(fully_read_event_id);
106        txn.commit();
107    }
108
109    #[instrument(skip_all)]
110    pub(super) async fn handle_ephemeral_events<P: RoomDataProvider>(
111        &mut self,
112        events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
113        room_data_provider: &P,
114    ) {
115        if events.is_empty() {
116            return;
117        }
118
119        let mut txn = self.transaction();
120
121        trace!("Handling ephemeral room events");
122        let own_user_id = room_data_provider.own_user_id();
123        for raw_event in events {
124            match raw_event.deserialize() {
125                Ok(AnySyncEphemeralRoomEvent::Receipt(ev)) => {
126                    txn.handle_explicit_read_receipts(ev.content, own_user_id);
127                }
128                Ok(_) => {}
129                Err(e) => {
130                    let event_type = raw_event.get_field::<String>("type").ok().flatten();
131                    warn!(event_type, "Failed to deserialize ephemeral event: {e}");
132                }
133            }
134        }
135
136        txn.commit();
137    }
138
139    /// Adds a local echo (for an event) to the timeline.
140    #[allow(clippy::too_many_arguments)]
141    #[instrument(skip_all)]
142    pub(super) async fn handle_local_event(
143        &mut self,
144        own_user_id: OwnedUserId,
145        own_profile: Option<Profile>,
146        should_add_new_items: bool,
147        date_divider_mode: DateDividerMode,
148        txn_id: OwnedTransactionId,
149        send_handle: Option<SendHandle>,
150        content: TimelineEventKind,
151    ) {
152        let ctx = TimelineEventContext {
153            sender: own_user_id,
154            sender_profile: own_profile,
155            timestamp: MilliSecondsSinceUnixEpoch::now(),
156            is_own_event: true,
157            read_receipts: Default::default(),
158            // An event sent by ourselves is never matched against push rules.
159            is_highlighted: false,
160            flow: Flow::Local { txn_id, send_handle },
161            should_add_new_items,
162        };
163
164        let mut txn = self.transaction();
165
166        let mut date_divider_adjuster = DateDividerAdjuster::new(date_divider_mode);
167
168        TimelineEventHandler::new(&mut txn, ctx)
169            .handle_event(&mut date_divider_adjuster, content)
170            .await;
171
172        txn.adjust_date_dividers(date_divider_adjuster);
173
174        txn.commit();
175    }
176
177    pub(super) async fn retry_event_decryption<P: RoomDataProvider, Fut>(
178        &mut self,
179        retry_one: impl Fn(Arc<TimelineItem>) -> Fut,
180        retry_indices: Vec<usize>,
181        push_rules_context: Option<(ruma::push::Ruleset, ruma::push::PushConditionRoomCtx)>,
182        room_data_provider: &P,
183        settings: &TimelineSettings,
184    ) where
185        Fut: Future<Output = Option<TimelineEvent>>,
186    {
187        let mut txn = self.transaction();
188
189        let mut date_divider_adjuster =
190            DateDividerAdjuster::new(settings.date_divider_mode.clone());
191
192        // Loop through all the indices, in order so we don't decrypt edits
193        // before the event being edited, if both were UTD. Keep track of
194        // index change as UTDs are removed instead of updated.
195        let mut offset = 0;
196        for idx in retry_indices {
197            let idx = idx - offset;
198            let Some(mut event) = retry_one(txn.items[idx].clone()).await else {
199                continue;
200            };
201
202            event.push_actions = push_rules_context.as_ref().map(|(push_rules, push_context)| {
203                push_rules.get_actions(event.raw(), push_context).to_owned()
204            });
205
206            let handle_one_res = txn
207                .handle_remote_event(
208                    event,
209                    TimelineItemPosition::UpdateAt { timeline_item_index: idx },
210                    room_data_provider,
211                    settings,
212                    &mut date_divider_adjuster,
213                )
214                .await;
215
216            // If the UTD was removed rather than updated, offset all
217            // subsequent loop iterations.
218            if handle_one_res.item_removed {
219                offset += 1;
220            }
221        }
222
223        txn.adjust_date_dividers(date_divider_adjuster);
224
225        txn.commit();
226    }
227
228    #[cfg(test)]
229    pub(super) fn handle_read_receipts(
230        &mut self,
231        receipt_event_content: ReceiptEventContent,
232        own_user_id: &UserId,
233    ) {
234        let mut txn = self.transaction();
235        txn.handle_explicit_read_receipts(receipt_event_content, own_user_id);
236        txn.commit();
237    }
238
239    pub(super) fn clear(&mut self) {
240        let mut txn = self.transaction();
241        txn.clear();
242        txn.commit();
243    }
244
245    /// Replaces the existing events in the timeline with the given remote ones.
246    ///
247    /// Note: when the `position` is [`TimelineEnd::Front`], prepended events
248    /// should be ordered in *reverse* topological order, that is, `events[0]`
249    /// is the most recent.
250    pub(super) async fn replace_with_remote_events<Events, RoomData>(
251        &mut self,
252        events: Events,
253        origin: RemoteEventOrigin,
254        room_data_provider: &RoomData,
255        settings: &TimelineSettings,
256    ) where
257        Events: IntoIterator,
258        Events::Item: Into<TimelineEvent>,
259        RoomData: RoomDataProvider,
260    {
261        let mut txn = self.transaction();
262        txn.clear();
263        txn.handle_remote_events_with_diffs(
264            vec![VectorDiff::Append { values: events.into_iter().map(Into::into).collect() }],
265            origin,
266            room_data_provider,
267            settings,
268        )
269        .await;
270        txn.commit();
271    }
272
273    pub(super) fn mark_all_events_as_encrypted(&mut self) {
274        // When this transaction finishes, all items in the timeline will be emitted
275        // again with the updated encryption value.
276        let mut txn = self.transaction();
277        txn.mark_all_events_as_encrypted();
278        txn.commit();
279    }
280
281    pub(super) fn transaction(&mut self) -> TimelineStateTransaction<'_> {
282        TimelineStateTransaction::new(&mut self.items, &mut self.meta, self.timeline_focus)
283    }
284}
285
286#[derive(Clone)]
287pub(in crate::timeline) enum PendingEditKind {
288    RoomMessage(Replacement<RoomMessageEventContentWithoutRelation>),
289    Poll(Replacement<NewUnstablePollStartEventContentWithoutRelation>),
290}
291
292#[derive(Clone)]
293pub(in crate::timeline) struct PendingEdit {
294    pub kind: PendingEditKind,
295    pub event_json: Raw<AnySyncTimelineEvent>,
296}
297
298impl PendingEdit {
299    pub fn edited_event(&self) -> &EventId {
300        match &self.kind {
301            PendingEditKind::RoomMessage(Replacement { event_id, .. })
302            | PendingEditKind::Poll(Replacement { event_id, .. }) => event_id,
303        }
304    }
305}
306
307#[cfg(not(tarpaulin_include))]
308impl std::fmt::Debug for PendingEdit {
309    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310        match &self.kind {
311            PendingEditKind::RoomMessage(_) => {
312                f.debug_struct("RoomMessage").finish_non_exhaustive()
313            }
314            PendingEditKind::Poll(_) => f.debug_struct("Poll").finish_non_exhaustive(),
315        }
316    }
317}
318
319/// Full metadata about an event.
320///
321/// Only used to group function parameters.
322pub(crate) struct FullEventMeta<'a> {
323    /// The ID of the event.
324    pub event_id: &'a EventId,
325    /// Whether the event is among the timeline items.
326    pub visible: bool,
327    /// The sender of the event.
328    pub sender: Option<&'a UserId>,
329    /// Whether this event was sent by our own user.
330    pub is_own_event: bool,
331    /// The timestamp of the event.
332    pub timestamp: Option<MilliSecondsSinceUnixEpoch>,
333}
334
335impl FullEventMeta<'_> {
336    pub(super) fn base_meta(&self) -> EventMeta {
337        EventMeta {
338            event_id: self.event_id.to_owned(),
339            visible: self.visible,
340            timeline_item_index: None,
341        }
342    }
343}