1use std::sync::Arc;
16
17use matrix_sdk::Room;
18use matrix_sdk_base::{SendOutsideWasm, SyncOutsideWasm};
19use ruma::{events::AnySyncTimelineEvent, room_version_rules::RoomVersionRules};
20use tracing::{Instrument, Span, info_span};
21
22use super::{
23 DateDividerMode, Error, Timeline, TimelineDropHandle, TimelineFocus,
24 controller::{TimelineController, TimelineSettings},
25};
26use crate::{
27 timeline::{
28 TimelineReadReceiptTracking,
29 controller::{ActiveCallInfo, InitFocusResult, spawn_crypto_tasks},
30 tasks::{
31 room_event_cache_updates_task, room_send_queue_update_task, rtc_membership_update_task,
32 },
33 traits::RoomDataProvider,
34 },
35 unable_to_decrypt_hook::UtdHookManager,
36};
37
38#[must_use]
41#[derive(Debug)]
42pub struct TimelineBuilder {
43 room: Room,
44 settings: TimelineSettings,
45 focus: TimelineFocus,
46
47 unable_to_decrypt_hook: Option<Arc<UtdHookManager>>,
50
51 internal_id_prefix: Option<String>,
53}
54
55impl TimelineBuilder {
56 pub fn new(room: &Room) -> Self {
57 Self {
58 room: room.clone(),
59 settings: TimelineSettings::default(),
60 unable_to_decrypt_hook: None,
61 focus: TimelineFocus::Live { hide_threaded_events: false },
62 internal_id_prefix: None,
63 }
64 }
65
66 pub fn with_focus(mut self, focus: TimelineFocus) -> Self {
73 self.focus = focus;
74 self
75 }
76
77 pub fn with_unable_to_decrypt_hook(mut self, hook: Arc<UtdHookManager>) -> Self {
82 self.unable_to_decrypt_hook = Some(hook);
83 self
84 }
85
86 pub fn with_internal_id_prefix(mut self, prefix: String) -> Self {
91 self.internal_id_prefix = Some(prefix);
92 self
93 }
94
95 pub fn with_date_divider_mode(mut self, mode: DateDividerMode) -> Self {
98 self.settings.date_divider_mode = mode;
99 self
100 }
101
102 pub fn track_read_marker_and_receipts(mut self, tracking: TimelineReadReceiptTracking) -> Self {
105 self.settings.track_read_receipts = tracking;
106 self
107 }
108
109 pub fn event_filter<F>(mut self, filter: F) -> Self
135 where
136 F: Fn(&AnySyncTimelineEvent, &RoomVersionRules) -> bool
137 + SendOutsideWasm
138 + SyncOutsideWasm
139 + 'static,
140 {
141 self.settings.event_filter = Arc::new(filter);
142 self
143 }
144
145 pub fn add_failed_to_parse(mut self, add: bool) -> Self {
149 self.settings.add_failed_to_parse = add;
150 self
151 }
152
153 #[tracing::instrument(
155 skip(self),
156 fields(
157 room_id = ?self.room.room_id(),
158 track_read_receipts = ?self.settings.track_read_receipts,
159 )
160 )]
161 pub async fn build(self) -> Result<Timeline, Error> {
162 let Self { room, settings, unable_to_decrypt_hook, focus, internal_id_prefix } = self;
163
164 let client = room.client();
166 let event_cache = client.event_cache();
167 event_cache.subscribe()?;
168
169 let room_id = room.room_id();
170 let (room_event_cache, event_cache_drop) = event_cache.room(room_id).await?;
171 let (_, event_subscriber) = room_event_cache.subscribe().await?;
172
173 let is_room_encrypted = room
174 .latest_encryption_state()
175 .await
176 .map(|state| state.is_encrypted())
177 .ok()
178 .unwrap_or_default();
179
180 let initial_info = room.clone_info();
181 let owned_user_id = room.own_user_id().to_owned();
182
183 let controller = TimelineController::new(
184 room.clone(),
185 &focus,
186 event_cache,
187 internal_id_prefix.clone(),
188 unable_to_decrypt_hook,
189 is_room_encrypted,
190 settings,
191 )
192 .await?;
193
194 let InitFocusResult { focus_task, has_events } = controller.init_focus().await?;
195
196 let room_update_join_handle = room
197 .client()
198 .task_monitor()
199 .spawn_infinite_task("timeline::room_event_cache_updates", {
200 let span = info_span!(
201 parent: Span::none(),
202 "live_update_handler",
203 room_id = ?room.room_id(),
204 focus = focus.debug_string(),
205 prefix = internal_id_prefix
206 );
207 span.follows_from(Span::current());
208
209 room_event_cache_updates_task(
210 room_event_cache.clone(),
211 controller.clone(),
212 event_subscriber,
213 focus.clone(),
214 )
215 .instrument(span)
216 })
217 .abort_on_drop();
218
219 let local_echo_listener_handle = {
220 let timeline_controller = controller.clone();
221 let (local_echoes, send_queue_stream) = room.send_queue().subscribe().await?;
222
223 room.client()
224 .task_monitor()
225 .spawn_infinite_task("timeline::local_echo_listener", {
226 for echo in local_echoes {
228 timeline_controller.handle_local_echo(echo).await;
229 }
230
231 let span = info_span!(
232 parent: Span::none(),
233 "local_echo_handler",
234 room_id = ?room.room_id(),
235 focus = focus.debug_string(),
236 prefix = internal_id_prefix
237 );
238 span.follows_from(Span::current());
239
240 room_send_queue_update_task(send_queue_stream, timeline_controller)
241 .instrument(span)
242 })
243 .abort_on_drop()
244 };
245
246 let initial_active_call_info = ActiveCallInfo::from_info(initial_info, owned_user_id);
247 if initial_active_call_info.is_some() {
248 controller.handle_active_call_update(initial_active_call_info).await;
249 }
250 let rtc_membership_listener_handle = {
251 let room_info_subscriber = room.subscribe_info();
252 room.client()
253 .task_monitor()
254 .spawn_infinite_task("timeline::rtc_membership_listener", {
255 let span = info_span!(
256 parent: Span::none(),
257 "rtc_membership_handler",
258 room_id = ?room.room_id(),
259 );
260 span.follows_from(Span::current());
261
262 rtc_membership_update_task(room_info_subscriber, controller.clone())
263 .instrument(span)
264 })
265 .abort_on_drop()
266 };
267
268 let crypto_drop_handles = spawn_crypto_tasks(controller.clone()).await;
269
270 let timeline = Timeline {
271 controller,
272 drop_handle: Arc::new(TimelineDropHandle {
273 _crypto_drop_handles: crypto_drop_handles,
274 _room_update_join_handle: room_update_join_handle,
275 _local_echo_listener_handle: local_echo_listener_handle,
276 _rtc_membership_listener_handle: rtc_membership_listener_handle,
277 _focus_drop_handle: focus_task,
278 _event_cache_drop_handle: event_cache_drop,
279 }),
280 };
281
282 if has_events {
283 timeline.retry_decryption_for_all_events().await;
287 }
288
289 Ok(timeline)
290 }
291}