matrix_sdk/latest_events/
room_latest_events.rs1use std::{collections::HashMap, ops::ControlFlow, sync::Arc};
16
17use matrix_sdk_base::RoomInfoNotableUpdateReasons;
18use ruma::{EventId, OwnedEventId, UserId, events::room::power_levels::RoomPowerLevels};
19use tokio::sync::{OnceCell, OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock};
20use tracing::{debug, error, instrument, warn};
21
22use super::{
23 LatestEvent, filter_timeline_event,
24 latest_event::{IsLatestEventValueNone, NeedMoreEvents, With},
25};
26use crate::{
27 Room,
28 event_cache::{
29 BackPaginationOutcome, EventCache, EventCacheError, RoomEventCache,
30 back_pagination_queue::{self, BackPaginationRequest},
31 },
32 room::WeakRoom,
33 send_queue::RoomSendQueueUpdate,
34};
35
36#[derive(Debug)]
38pub(super) struct RoomLatestEvents {
39 state: Arc<RwLock<RoomLatestEventsState>>,
41}
42
43impl RoomLatestEvents {
44 pub fn new(
46 weak_room: WeakRoom,
47 event_cache: &EventCache,
48 ) -> With<Self, IsLatestEventValueNone> {
49 let latest_event_with = Self::create_latest_event(&weak_room, None);
50
51 With::map(latest_event_with, |for_the_room| Self {
52 state: Arc::new(RwLock::new(RoomLatestEventsState {
53 for_the_room,
54 per_thread: HashMap::new(),
55 weak_room,
56 event_cache: event_cache.clone(),
57 room_event_cache: OnceCell::new(),
58 })),
59 })
60 }
61
62 fn create_latest_event(
63 weak_room: &WeakRoom,
64 thread_id: Option<&EventId>,
65 ) -> With<LatestEvent, IsLatestEventValueNone> {
66 LatestEvent::new(weak_room, thread_id)
67 }
68
69 pub async fn read(&self) -> RoomLatestEventsReadGuard {
71 RoomLatestEventsReadGuard { inner: self.state.clone().read_owned().await }
72 }
73
74 pub async fn write(&self) -> RoomLatestEventsWriteGuard {
77 RoomLatestEventsWriteGuard { inner: self.state.clone().write_owned().await }
78 }
79}
80
81#[derive(Debug)]
83struct RoomLatestEventsState {
84 for_the_room: LatestEvent,
86
87 per_thread: HashMap<OwnedEventId, LatestEvent>,
89
90 event_cache: EventCache,
92
93 room_event_cache: OnceCell<RoomEventCache>,
95
96 weak_room: WeakRoom,
101}
102
103pub(super) struct RoomLatestEventsReadGuard {
105 inner: OwnedRwLockReadGuard<RoomLatestEventsState>,
106}
107
108impl RoomLatestEventsReadGuard {
109 pub fn for_room(&self) -> &LatestEvent {
111 &self.inner.for_the_room
112 }
113
114 pub fn for_thread(&self, thread_id: &EventId) -> Option<&LatestEvent> {
116 self.inner.per_thread.get(thread_id)
117 }
118
119 #[cfg(test)]
120 pub fn per_thread(&self) -> &HashMap<OwnedEventId, LatestEvent> {
121 &self.inner.per_thread
122 }
123}
124
125pub(super) struct RoomLatestEventsWriteGuard {
127 inner: OwnedRwLockWriteGuard<RoomLatestEventsState>,
128}
129
130impl RoomLatestEventsWriteGuard {
131 pub fn has_thread(&self, thread_id: &EventId) -> bool {
134 self.inner.per_thread.contains_key(thread_id)
135 }
136
137 pub fn create_and_insert_latest_event_for_thread(&mut self, thread_id: &EventId) {
140 let latest_event_with =
141 RoomLatestEvents::create_latest_event(&self.inner.weak_room, Some(thread_id));
142
143 self.inner.per_thread.insert(thread_id.to_owned(), With::inner(latest_event_with));
144 }
145
146 pub fn forget_thread(&mut self, thread_id: &EventId) {
148 self.inner.per_thread.remove(thread_id);
149 }
150
151 pub async fn update_with_event_cache(&mut self) {
154 let Some(room) = self.inner.weak_room.get() else {
160 error!(room = ?self.inner.weak_room, "Room is unknown");
162
163 return;
164 };
165 let own_user_id = room.own_user_id();
166 let power_levels = room.power_levels().await.ok();
167
168 let inner = &mut *self.inner;
169 let for_the_room = &mut inner.for_the_room;
170 let per_thread = &mut inner.per_thread;
171
172 let room_event_cache = match inner
174 .room_event_cache
175 .get_or_try_init(|| async {
176 let (room_event_cache, _drop_handles) =
179 inner.event_cache.room(room.room_id()).await?;
180
181 Ok::<RoomEventCache, EventCacheError>(room_event_cache)
182 })
183 .await
184 {
185 Ok(room_event_cache) => room_event_cache,
186 Err(err) => {
187 error!(room_id = ?room.room_id(), ?err, "Failed to fetch the `RoomEventCache`");
188 return;
189 }
190 };
191
192 if matches!(
195 for_the_room
196 .update_with_event_cache(room_event_cache, own_user_id, power_levels.as_ref())
197 .await,
198 NeedMoreEvents::Yes
199 ) {
200 Self::back_paginate_for_candidate(&room, own_user_id, power_levels.as_ref());
201 }
202
203 for latest_event in per_thread.values_mut() {
204 latest_event
205 .update_with_event_cache(room_event_cache, own_user_id, power_levels.as_ref())
206 .await;
207 }
208 }
209
210 pub async fn update_with_send_queue(&mut self, send_queue_update: &RoomSendQueueUpdate) {
213 let Some(room) = self.inner.weak_room.get() else {
219 return;
221 };
222 let own_user_id = room.own_user_id();
223 let power_levels = room.power_levels().await.ok();
224
225 let inner = &mut *self.inner;
226 let for_the_room = &mut inner.for_the_room;
227 let per_thread = &mut inner.per_thread;
228
229 let room_event_cache = match inner
231 .room_event_cache
232 .get_or_try_init(|| async {
233 let (room_event_cache, _drop_handles) =
236 inner.event_cache.room(room.room_id()).await?;
237
238 Ok::<RoomEventCache, EventCacheError>(room_event_cache)
239 })
240 .await
241 {
242 Ok(room_event_cache) => room_event_cache,
243 Err(err) => {
244 error!(room_id = ?room.room_id(), ?err, "Failed to fetch the `RoomEventCache`");
245 return;
246 }
247 };
248
249 for_the_room
250 .update_with_send_queue(
251 send_queue_update,
252 room_event_cache,
253 own_user_id,
254 power_levels.as_ref(),
255 )
256 .await;
257
258 for latest_event in per_thread.values_mut() {
259 latest_event
260 .update_with_send_queue(
261 send_queue_update,
262 room_event_cache,
263 own_user_id,
264 power_levels.as_ref(),
265 )
266 .await;
267 }
268 }
269
270 pub async fn update_with_room_info(&mut self, reasons: RoomInfoNotableUpdateReasons) {
273 let Some(room) = self.inner.weak_room.get() else {
275 return;
277 };
278
279 self.inner.for_the_room.update_with_room_info(room, reasons).await;
280 }
281
282 #[instrument(skip_all, fields(room_id = %room.room_id()))]
295 fn back_paginate_for_candidate(
296 room: &Room,
297 own_user_id: &UserId,
298 power_levels: Option<&RoomPowerLevels>,
299 ) {
300 let Some(queue) = room.client().event_cache().back_pagination_queue() else {
301 return;
302 };
303
304 let own_user_id = own_user_id.to_owned();
305 let power_levels = power_levels.cloned();
306
307 let stop = move |outcome: &BackPaginationOutcome| {
312 let found = outcome.events.iter().any(|event| {
313 filter_timeline_event(event, None, &own_user_id, power_levels.as_ref()).is_break()
314 });
315
316 if found { ControlFlow::Break(()) } else { ControlFlow::Continue(()) }
317 };
318
319 debug!("started backfill request for latest events");
320
321 match queue.enqueue(BackPaginationRequest {
322 room_id: room.room_id().to_owned(),
323 priority: back_pagination_queue::Priority::High,
324 stop: Box::new(stop),
325 batch_size: back_pagination_queue::BATCH_SIZE,
326 max_batches: None,
327 }) {
328 Ok(handle) => handle.detach(),
331 Err(err) => warn!("couldn't enqueue a latest-event backfill request: {err}"),
332 }
333 }
334}
335
336#[cfg(all(test, not(target_family = "wasm")))]
337mod tests {
338 use assert_matches::assert_matches;
339 use matrix_sdk_base::{
340 RoomState,
341 event_cache::Gap,
342 linked_chunk::{ChunkIdentifier, LinkedChunkId, Update},
343 };
344 use matrix_sdk_test::{async_test, event_factory::EventFactory};
345 use ruma::{event_id, room_id, user_id};
346
347 use super::RoomLatestEvents;
348 use crate::{
349 assert_let_timeout,
350 client::WeakClient,
351 latest_events::LatestEventValue,
352 room::WeakRoom,
353 test_utils::mocks::{MatrixMockServer, RoomMessagesResponseTemplate},
354 };
355
356 #[async_test]
359 async fn test_update_with_event_cache_backfills_for_a_candidate() {
360 let room_id = room_id!("!r0");
361 let sender = user_id!("@bob:example.org");
362
363 let server = MatrixMockServer::new().await;
364 let client = server
365 .client_builder()
366 .on_builder(|builder| builder.with_enable_automatic_back_pagination(true))
367 .build()
368 .await;
369
370 client.base_client().get_or_create_room(room_id, RoomState::Joined);
371
372 client
376 .event_cache_store()
377 .lock()
378 .await
379 .unwrap()
380 .as_clean()
381 .unwrap()
382 .handle_linked_chunk_updates(
383 LinkedChunkId::Room(room_id),
384 vec![Update::NewGapChunk {
385 previous: None,
386 new: ChunkIdentifier::new(0),
387 next: None,
388 gap: Gap { token: "prev_batch".to_owned() },
389 }],
390 )
391 .await
392 .unwrap();
393
394 let event_cache = client.event_cache();
395 event_cache.subscribe().unwrap();
396
397 let f = EventFactory::new().room(room_id).sender(sender);
399 server
400 .mock_room_messages()
401 .match_from("prev_batch")
402 .ok(RoomMessagesResponseTemplate::default()
403 .events(vec![f.text_msg("hello").event_id(event_id!("$1"))]))
404 .mock_once()
405 .mount()
406 .await;
407
408 let weak_room = WeakRoom::new(WeakClient::from_client(&client), room_id.to_owned());
409 let room_latest_events = RoomLatestEvents::new(weak_room, event_cache);
410
411 assert_matches!(
413 room_latest_events.read().await.for_room().get().await,
414 LatestEventValue::None
415 );
416
417 let mut updates = event_cache.subscribe_to_room_generic_updates();
418
419 room_latest_events.write().await.update_with_event_cache().await;
420
421 assert_let_timeout!(Ok(_) = updates.recv());
424
425 room_latest_events.write().await.update_with_event_cache().await;
426
427 assert_matches!(
429 room_latest_events.read().await.for_room().get().await,
430 LatestEventValue::Remote(_)
431 );
432 }
433}