Skip to main content

matrix_sdk_ui/timeline/
tasks.rs

1// Copyright 2025 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
15//! Long-lived tasks for the timeline.
16
17use std::collections::BTreeSet;
18
19use eyeball::Subscriber as EyeballSubscriber;
20use matrix_sdk::{
21    event_cache::{
22        EventFocusThreadMode, EventFocusedCache, EventsOrigin, PinnedEventsCache, RoomEventCache,
23        RoomEventCacheUpdate, Subscriber, ThreadEventCache, ThreadEventCacheUpdate,
24        TimelineVectorDiffs,
25    },
26    send_queue::RoomSendQueueUpdate,
27};
28use matrix_sdk_base::RoomInfo;
29use ruma::OwnedEventId;
30#[cfg(feature = "unstable-msc4426")]
31use ruma::{OwnedUserId, UserId};
32use tokio::sync::broadcast::{Receiver, error::RecvError};
33use tracing::{error, instrument, trace, warn};
34
35use crate::timeline::{
36    TimelineController, TimelineFocus, controller::ActiveCallInfo, event_item::RemoteEventOrigin,
37    traits::RoomDataProvider,
38};
39
40/// Long-lived task, in the pinned events focus mode, that updates the timeline
41/// after any changes in the pinned events.
42#[instrument(
43    skip_all,
44    fields(
45        room_id = %timeline_controller.room().room_id(),
46    )
47)]
48pub(in crate::timeline) async fn pinned_events_task(
49    pinned_events_cache: PinnedEventsCache,
50    timeline_controller: TimelineController,
51    mut pinned_events_recv: Receiver<TimelineVectorDiffs>,
52) {
53    loop {
54        trace!("Waiting for an event.");
55
56        let update = match pinned_events_recv.recv().await {
57            Ok(up) => up,
58            Err(RecvError::Closed) => break,
59            Err(RecvError::Lagged(num_skipped)) => {
60                warn!(num_skipped, "Lagged behind pinned-event cache updates, resetting timeline");
61
62                // The updates might have lagged, but the room event cache might have
63                // events, so retrieve them and add them back again to the timeline,
64                // after clearing it.
65                let (initial_events, _) = match pinned_events_cache.subscribe().await {
66                    Ok(initial_events) => initial_events,
67                    Err(err) => {
68                        error!(
69                            ?err,
70                            "Failed to replace the initial remote events in the event cache"
71                        );
72                        break;
73                    }
74                };
75
76                timeline_controller
77                    .replace_with_initial_remote_events(initial_events, RemoteEventOrigin::Cache)
78                    .await;
79
80                continue;
81            }
82        };
83
84        trace!("Received new timeline events diffs");
85        let origin = match update.origin {
86            EventsOrigin::Sync => RemoteEventOrigin::Sync,
87            EventsOrigin::Pagination => RemoteEventOrigin::Pagination,
88            EventsOrigin::Cache => RemoteEventOrigin::Cache,
89        };
90        timeline_controller.handle_remote_events_with_diffs(update.diffs, origin).await;
91    }
92}
93
94/// Long-lived task, in the event focus mode, that updates the timeline after
95/// any changes to the underlying timeline.
96#[instrument(
97    skip_all,
98    fields(
99        room_id = %timeline_controller.room().room_id(),
100        focused_event_id = %focused_event,
101        ?thread_mode
102    )
103)]
104pub(in crate::timeline) async fn event_focused_task(
105    focused_event: OwnedEventId,
106    thread_mode: EventFocusThreadMode,
107    event_cache: EventFocusedCache,
108    timeline_controller: TimelineController,
109    mut event_focused_events_recv: Receiver<TimelineVectorDiffs>,
110) {
111    loop {
112        trace!("Waiting for an event.");
113
114        let update = match event_focused_events_recv.recv().await {
115            Ok(up) => up,
116            Err(RecvError::Closed) => break,
117            Err(RecvError::Lagged(num_skipped)) => {
118                warn!(num_skipped, "Lagged behind focused-event cache updates, resetting timeline");
119
120                // The updates might have lagged, but the room event cache might have
121                // events, so retrieve them and add them back again to the timeline,
122                // after clearing it.
123                let Ok((initial_events, _)) = event_cache.subscribe().await else {
124                    error!("Failed to subscribe to the event-focused cache");
125                    break;
126                };
127
128                timeline_controller
129                    .replace_with_initial_remote_events(initial_events, RemoteEventOrigin::Cache)
130                    .await;
131
132                continue;
133            }
134        };
135
136        trace!("Received new timeline events diffs");
137        let origin = match update.origin {
138            EventsOrigin::Sync => RemoteEventOrigin::Sync,
139            EventsOrigin::Pagination => RemoteEventOrigin::Pagination,
140            EventsOrigin::Cache => RemoteEventOrigin::Cache,
141        };
142        timeline_controller.handle_remote_events_with_diffs(update.diffs, origin).await;
143    }
144}
145
146/// For a thread-focused timeline, a long-lived task that will listen to the
147/// underlying thread updates.
148pub(in crate::timeline) async fn thread_updates_task(
149    mut thread_event_cache_subscriber: Subscriber<ThreadEventCacheUpdate>,
150    event_cache: ThreadEventCache,
151    timeline_controller: TimelineController,
152) {
153    trace!("Spawned the thread event subscriber task.");
154
155    loop {
156        trace!("Waiting for an event.");
157
158        let update = match thread_event_cache_subscriber.recv().await {
159            Ok(up) => up,
160            Err(RecvError::Closed) => break,
161            Err(RecvError::Lagged(num_skipped)) => {
162                warn!(num_skipped, "Lagged behind event cache updates, resetting timeline");
163
164                // The updates might have lagged, but the room event cache might
165                // have events, so retrieve them and add them back again to the
166                // timeline, after clearing it.
167                _ = timeline_controller.init_with_thread_root(&event_cache).await;
168
169                continue;
170            }
171        };
172
173        match update {
174            ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, origin }) => {
175                trace!("Received new timeline events diffs");
176
177                let origin = match origin {
178                    EventsOrigin::Sync => RemoteEventOrigin::Sync,
179                    EventsOrigin::Pagination => RemoteEventOrigin::Pagination,
180                    EventsOrigin::Cache => RemoteEventOrigin::Cache,
181                };
182
183                let has_diffs = !diffs.is_empty();
184
185                timeline_controller.handle_remote_events_with_diffs(diffs, origin).await;
186
187                if has_diffs && matches!(origin, RemoteEventOrigin::Cache) {
188                    timeline_controller.retry_event_decryption(None).await;
189                }
190            }
191
192            ThreadEventCacheUpdate::AddReadReceiptEvent { event } => {
193                trace!("Received a new read receipt event from sync.");
194
195                // TODO: ephemeral (read receipts) should be handled by the event cache (#4113).
196                timeline_controller.handle_read_receipt_event(event).await;
197            }
198        }
199    }
200
201    trace!("Thread event subscriber task finished.");
202}
203
204/// Long-lived task that forwards the [`RoomEventCacheUpdate`]s (remote echoes)
205/// to the timeline.
206pub(in crate::timeline) async fn room_event_cache_updates_task(
207    room_event_cache: RoomEventCache,
208    timeline_controller: TimelineController,
209    mut room_event_cache_subscriber: Subscriber<RoomEventCacheUpdate>,
210    timeline_focus: TimelineFocus,
211) {
212    trace!("Spawned the event subscriber task.");
213
214    loop {
215        trace!("Waiting for an event.");
216
217        let update = match room_event_cache_subscriber.recv().await {
218            Ok(up) => up,
219            Err(RecvError::Closed) => break,
220            Err(RecvError::Lagged(num_skipped)) => {
221                warn!(num_skipped, "Lagged behind event cache updates, resetting timeline");
222
223                // The updates might have lagged, but the room event cache might have
224                // events, so retrieve them and add them back again to the timeline,
225                // after clearing it.
226                let initial_events = match room_event_cache.events().await {
227                    Ok(initial_events) => initial_events,
228                    Err(err) => {
229                        error!(
230                            ?err,
231                            "Failed to replace the initial remote events in the event cache"
232                        );
233                        break;
234                    }
235                };
236
237                timeline_controller
238                    .replace_with_initial_remote_events(initial_events, RemoteEventOrigin::Cache)
239                    .await;
240
241                continue;
242            }
243        };
244
245        match update {
246            RoomEventCacheUpdate::MoveReadMarkerTo { event_id } => {
247                trace!(target = %event_id, "Handling fully read marker.");
248                timeline_controller.handle_fully_read_marker(event_id).await;
249            }
250
251            RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, origin }) => {
252                trace!("Received new timeline events diffs");
253                let origin = match origin {
254                    EventsOrigin::Sync => RemoteEventOrigin::Sync,
255                    EventsOrigin::Pagination => RemoteEventOrigin::Pagination,
256                    EventsOrigin::Cache => RemoteEventOrigin::Cache,
257                };
258
259                let has_diffs = !diffs.is_empty();
260
261                if matches!(timeline_focus, TimelineFocus::Live { .. }) {
262                    timeline_controller.handle_remote_events_with_diffs(diffs, origin).await;
263                } else if matches!(timeline_focus, TimelineFocus::Event { .. }) {
264                    // Only handle the remote aggregation for an event-focused timeline.
265                    timeline_controller.handle_remote_aggregations(diffs, origin).await;
266                }
267
268                if has_diffs && matches!(origin, RemoteEventOrigin::Cache) {
269                    timeline_controller.retry_event_decryption(None).await;
270                }
271            }
272
273            RoomEventCacheUpdate::AddReadReceiptEvent { event } => {
274                trace!("Received a new read receipt event from sync.");
275
276                // TODO: ephemeral (read receipts) should be handled by the event cache (#4113).
277                timeline_controller.handle_read_receipt_event(event).await;
278            }
279
280            RoomEventCacheUpdate::UpdateMembers { ambiguity_changes, avatar_changes } => {
281                if !ambiguity_changes.is_empty()
282                    || !avatar_changes.as_ref().is_none_or(|avatars| avatars.is_empty())
283                {
284                    let member_ambiguity_changes = ambiguity_changes
285                        .values()
286                        .flat_map(|change| change.user_ids())
287                        .collect::<BTreeSet<_>>();
288
289                    let mut user_ids_to_update = member_ambiguity_changes;
290
291                    if let Some(avatar_changes) = &avatar_changes {
292                        let mut user_ids =
293                            avatar_changes.keys().map(|u| u.as_ref()).collect::<BTreeSet<_>>();
294                        user_ids_to_update.append(&mut user_ids)
295                    } else {
296                        warn!(
297                            "No avatar changes to update for {}, ignoring",
298                            room_event_cache.room_id()
299                        );
300                    }
301                    timeline_controller.force_update_sender_profiles(&user_ids_to_update).await;
302                }
303            }
304        }
305    }
306}
307
308/// Long-lived task that refreshes displayed sender profiles when the users'
309/// global profiles change. The controller filters to the senders it shows.
310#[cfg(feature = "unstable-msc4426")]
311pub(in crate::timeline) async fn global_profile_updates_task(
312    mut global_profile_updates_stream: Receiver<BTreeSet<OwnedUserId>>,
313    timeline_controller: TimelineController,
314) {
315    trace!("spawned the global profile updates task!");
316
317    loop {
318        match global_profile_updates_stream.recv().await {
319            Ok(user_ids) => {
320                let sender_ids: BTreeSet<&UserId> =
321                    user_ids.iter().map(|user_id| user_id.as_ref()).collect();
322                timeline_controller.force_update_sender_profiles(&sender_ids).await;
323            }
324
325            Err(RecvError::Lagged(num_missed)) => {
326                warn!("missed {num_missed} global profile updates, ignoring those missed");
327            }
328
329            Err(RecvError::Closed) => {
330                trace!("channel closed, exiting the global profile updates handler");
331                break;
332            }
333        }
334    }
335}
336
337/// Long-lived task that forwards [`RoomSendQueueUpdate`]s (local echoes) to the
338/// timeline.
339pub(in crate::timeline) async fn room_send_queue_update_task(
340    mut send_queue_stream: Receiver<RoomSendQueueUpdate>,
341    timeline_controller: TimelineController,
342) {
343    trace!("spawned the local echo task!");
344
345    loop {
346        match send_queue_stream.recv().await {
347            Ok(update) => timeline_controller.handle_room_send_queue_update(update).await,
348
349            Err(RecvError::Lagged(num_missed)) => {
350                warn!("missed {num_missed} local echoes, ignoring those missed");
351            }
352
353            Err(RecvError::Closed) => {
354                trace!("channel closed, exiting the local echo handler");
355                break;
356            }
357        }
358    }
359}
360
361/// Long-lived task that watches RoomInfo for RTC membership changes
362/// and updates the active RtcNotification timeline item.
363pub(in crate::timeline) async fn rtc_membership_update_task(
364    mut room_info: EyeballSubscriber<RoomInfo>,
365    timeline_controller: TimelineController,
366    initial_call_info: Option<ActiveCallInfo>,
367) {
368    let mut prev_info = initial_call_info;
369    let own_user = timeline_controller.room().own_user_id().to_owned();
370
371    while let Some(info) = room_info.next().await {
372        let active_call = ActiveCallInfo::from_info(info, own_user.clone());
373        // RoomInfo fires for many reasons; only act when the participant
374        // list actually changed.
375        if active_call != prev_info {
376            prev_info = active_call.clone();
377            timeline_controller.handle_active_call_update(active_call).await;
378        }
379    }
380}