Skip to main content

matrix_sdk/latest_events/latest_event/
mod.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
15mod builder;
16
17use std::ops::{Deref, DerefMut, Not};
18
19pub use builder::filter_timeline_event;
20use builder::{BufferOfValuesForLocalEvents, Builder};
21use eyeball::{AsyncLock, ObservableWriteGuard, SharedObservable, Subscriber};
22pub use matrix_sdk_base::latest_event::{
23    LatestEventValue, LocalLatestEventValue, RemoteLatestEventValue,
24};
25use matrix_sdk_base::{RoomInfoNotableUpdateReasons, RoomState};
26use ruma::{EventId, OwnedEventId, UserId, events::room::power_levels::RoomPowerLevels};
27use tracing::{error, info, instrument, trace, warn};
28
29use crate::{Room, event_cache::RoomEventCache, room::WeakRoom, send_queue::RoomSendQueueUpdate};
30
31/// Whether back-paginating a room could give its latest event a value it
32/// cannot compute from what's currently in memory.
33#[derive(Clone, Copy, Debug)]
34pub(super) enum NeedMoreEvents {
35    Yes,
36    No,
37}
38
39/// The latest event of a room or a thread.
40///
41/// Use [`LatestEvent::subscribe`] to get a stream of updates.
42#[derive(Debug)]
43pub(super) struct LatestEvent {
44    /// The room owning this latest event.
45    weak_room: WeakRoom,
46
47    /// The thread (if any) owning this latest event.
48    _thread_id: Option<OwnedEventId>,
49
50    /// A buffer of the current [`LatestEventValue`]s computed for local events
51    /// seen by the send queue. See [`BufferOfValuesForLocalEvents`] to learn
52    /// more.
53    buffer_of_values_for_local_events: BufferOfValuesForLocalEvents,
54
55    /// The latest event value.
56    current_value: SharedObservable<LatestEventValue, AsyncLock>,
57}
58
59impl LatestEvent {
60    pub fn new(
61        weak_room: &WeakRoom,
62        thread_id: Option<&EventId>,
63    ) -> With<Self, IsLatestEventValueNone> {
64        let latest_event_value = match thread_id {
65            Some(_thread_id) => LatestEventValue::default(),
66            None => weak_room.get().map(|room| room.latest_event()).unwrap_or_default(),
67        };
68        let is_none = latest_event_value.is_none();
69
70        With {
71            result: Self {
72                weak_room: weak_room.clone(),
73                _thread_id: thread_id.map(ToOwned::to_owned),
74                buffer_of_values_for_local_events: BufferOfValuesForLocalEvents::new(),
75                current_value: SharedObservable::new_async(latest_event_value),
76            },
77            with: is_none,
78        }
79    }
80
81    /// Return a [`Subscriber`] to new values.
82    pub async fn subscribe(&self) -> Subscriber<LatestEventValue, AsyncLock> {
83        self.current_value.subscribe().await
84    }
85
86    #[cfg(test)]
87    pub async fn get(&self) -> LatestEventValue {
88        self.current_value.get().await
89    }
90
91    /// Update the inner latest event value, based on the event cache
92    /// (specifically with the [`RoomEventCache`]), if and only if there is no
93    /// local latest event value waiting.
94    ///
95    /// It is only necessary to compute a new [`LatestEventValue`] from the
96    /// event cache if there is no [`LatestEventValue`] to be compute from the
97    /// send queue. Indeed, anything coming from the send queue has the priority
98    /// over the anything coming from the event cache. We believe it provides a
99    /// better user experience.
100    ///
101    /// Returns whether back-paginating the room could yield a value that can't
102    /// be computed from what's currently in memory.
103    pub async fn update_with_event_cache(
104        &mut self,
105        room_event_cache: &RoomEventCache,
106        own_user_id: &UserId,
107        power_levels: Option<&RoomPowerLevels>,
108    ) -> NeedMoreEvents {
109        if self.buffer_of_values_for_local_events.is_empty().not() {
110            // At least one `LatestEventValue` exists for local events (i.e. coming from the
111            // send queue). In this case, we don't overwrite the current value with a newly
112            // computed one from the event cache.
113            return NeedMoreEvents::No;
114        }
115
116        let current_event = self.current_value.get().await;
117        let new_value =
118            Builder::new_remote(room_event_cache, current_event, own_user_id, power_levels).await;
119
120        trace!(value = ?new_value, "Computed a remote `LatestEventValue`");
121
122        let need_more_events = match new_value {
123            Some(LatestEventValue::Remote(_)) => NeedMoreEvents::No,
124            _ => NeedMoreEvents::Yes,
125        };
126
127        if let Some(new_value) = new_value {
128            self.update(new_value).await;
129        }
130
131        need_more_events
132    }
133
134    /// Update the inner latest event value, based on the send queue
135    /// (specifically with the [`RoomSendQueueUpdate`]).
136    pub async fn update_with_send_queue(
137        &mut self,
138        send_queue_update: &RoomSendQueueUpdate,
139        room_event_cache: &RoomEventCache,
140        own_user_id: &UserId,
141        power_levels: Option<&RoomPowerLevels>,
142    ) {
143        let current_event = self.current_value.get().await;
144        let new_value = Builder::new_local(
145            send_queue_update,
146            &mut self.buffer_of_values_for_local_events,
147            room_event_cache,
148            current_event,
149            own_user_id,
150            power_levels,
151        )
152        .await;
153
154        trace!(value = ?new_value, "Computed a local `LatestEventValue`");
155
156        if let Some(new_value) = new_value {
157            self.update(new_value).await;
158        }
159    }
160
161    /// Update the inner latest event value, based on the room info.
162    pub async fn update_with_room_info(
163        &mut self,
164        room: Room,
165        reasons: RoomInfoNotableUpdateReasons,
166    ) {
167        // If the `RoomInfo` has been updated due to a change of the own membership.
168        if reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP) {
169            let new_value = match room.state() {
170                // If the room' state is `Invited`, it means the current user has been recently
171                // invited to this room.
172                RoomState::Invited => {
173                    // Short: Let's not update a `RemoteInvite` to another `RemoteInvite`.
174                    //
175                    // Long: An invite room is only constituted of stripped-state events. These
176                    // events do not have an `origin_server_ts` field. It means we cannot compute
177                    // the timestamp of the `LatestEventValue`. To workaround this, we set the
178                    // timestamp to `now()`. See `Builder::new_remote_for_invite` to learn more.
179                    // If an invite room receives a new event, its `LatestEventValue`'s timestamp
180                    // will be updated to `now()`, which will make the room bumps to the top of the
181                    // room list for example. This is not an acceptable behaviour because it can be
182                    // an “attack vector”, i.e. a way to annoy people with spammy invites. That's
183                    // why, once a `RemoteInvite` has been computed, we do not refresh it.
184                    if matches!(
185                        self.current_value.read().await.deref(),
186                        LatestEventValue::RemoteInvite { .. }
187                    ) {
188                        return;
189                    }
190
191                    let new_value = Builder::new_remote_for_invite(&room).await;
192
193                    trace!(value = ?new_value, "Computed a remote `LatestEventValue` for invite");
194
195                    new_value
196                }
197
198                _ => {
199                    info!(
200                        "Skipping the computation of a remote `LatestEventValue` from a `RoomInfo`"
201                    );
202
203                    return;
204                }
205            };
206
207            self.update(new_value).await;
208        }
209    }
210
211    /// Update [`Self::current_value`], and persist the `new_value` in the
212    /// store.
213    ///
214    /// If the `new_value` is [`LatestEventValue::None`], it is accepted: if the
215    /// previous latest event value has been redacted and no other candidate has
216    /// been found, we want to latest event value to be `None`, so that it is
217    /// erased correctly.
218    async fn update(&mut self, new_value: LatestEventValue) {
219        // Ideally, we would set `new_value` if and only if it is different from the
220        // previous value. However, `LatestEventValue` cannot implement `PartialEq` at
221        // the time of writing (2025-12-12). So we are only updating if:
222        //
223        // - if `LatestEventValue` and the previous value aren't `None`,
224        // - if the event IDs are different.
225        //
226        // We must be careful when comparing the event IDs: `None` and `Local*` have no
227        // event ID, we can't compare them at this point. Hence the `match` statement to
228        // have a fine-grained decision.
229        {
230            let mut guard = self.current_value.write().await;
231            let previous_value = guard.deref();
232
233            let do_update = match (previous_value, &new_value) {
234                // If both are `None`, no.
235                (LatestEventValue::None, LatestEventValue::None) => false,
236
237                // If at least one is `None`, yes.
238                (_, LatestEventValue::None) | (LatestEventValue::None, _) => true,
239
240                // A new local latest event is created, or the local event
241                // cannot be sent, yes.
242                (
243                    _,
244                    LatestEventValue::LocalIsSending(_) | LatestEventValue::LocalCannotBeSent(_),
245                ) => true,
246
247                // If both event IDs are known, do an update if they're different.
248                // If either is unknown, the two values cannot be compared, so do an update.
249                (previous, new) => match (previous.event_id(), new.event_id()) {
250                    (Some(previous_event_id), Some(new_event_id)) => {
251                        previous_event_id != new_event_id
252                    }
253                    _ => true,
254                },
255            };
256
257            if do_update {
258                ObservableWriteGuard::set(&mut guard, new_value.clone());
259
260                // Release the write guard over the current value before hitting the store.
261                drop(guard);
262
263                self.store(new_value).await;
264            }
265        }
266    }
267
268    /// Update the `RoomInfo` associated to this room to set the new
269    /// [`LatestEventValue`], and persist it in the
270    /// [`StateStore`][matrix_sdk_base::StateStore] (the one from
271    /// [`Client::state_store`][crate::Client::state_store]).
272    #[instrument(skip_all)]
273    async fn store(&mut self, new_value: LatestEventValue) {
274        let Some(room) = self.weak_room.get() else {
275            warn!(room_id = ?self.weak_room.room_id(), "Cannot store the latest event value because the room cannot be accessed");
276            return;
277        };
278        let result = room
279            .update_and_save_room_info(|mut info| {
280                info.set_latest_event(new_value);
281                (info, RoomInfoNotableUpdateReasons::LATEST_EVENT)
282            })
283            .await;
284        if let Err(error) = result {
285            error!(room_id = ?room.room_id(), ?error, "Failed to save the changes");
286        }
287    }
288}
289
290/// Semantic type similar to a tuple where the left part is the main result and
291/// the right part is an “attached” value.
292pub(super) struct With<T, W> {
293    /// The main value.
294    result: T,
295
296    /// The “attached” value.
297    with: W,
298}
299
300impl<T, W> With<T, W> {
301    /// Map the main result without changing the “attached” value.
302    pub fn map<F, O>(this: With<T, W>, f: F) -> With<O, W>
303    where
304        F: FnOnce(T) -> O,
305    {
306        With { result: f(this.result), with: this.with }
307    }
308
309    /// Get the main result.
310    pub fn inner(this: With<T, W>) -> T {
311        this.result
312    }
313
314    /// Get a tuple.
315    pub fn unzip(this: With<T, W>) -> (T, W) {
316        (this.result, this.with)
317    }
318}
319
320impl<T, W> Deref for With<T, W> {
321    type Target = T;
322
323    fn deref(&self) -> &Self::Target {
324        &self.result
325    }
326}
327
328impl<T, W> DerefMut for With<T, W> {
329    fn deref_mut(&mut self) -> &mut Self::Target {
330        &mut self.result
331    }
332}
333
334pub(super) type IsLatestEventValueNone = bool;
335
336#[cfg(all(not(target_family = "wasm"), test))]
337mod tests_latest_event {
338    use std::ops::Not;
339
340    use assert_matches::assert_matches;
341    use matrix_sdk_base::{
342        RoomInfoNotableUpdateReasons, RoomState,
343        latest_event::RemoteLatestEventValue,
344        linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update},
345        store::{SerializableEventContent, StoreConfig},
346    };
347    use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
348    use matrix_sdk_test::{JoinedRoomBuilder, async_test, event_factory::EventFactory};
349    use ruma::{
350        MilliSecondsSinceUnixEpoch, OwnedTransactionId, event_id,
351        events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
352        owned_event_id, owned_room_id, owned_user_id, room_id, user_id,
353    };
354    use stream_assert::{assert_next_matches, assert_pending};
355    use tokio::task::yield_now;
356
357    use super::{super::local_room_message, LatestEvent, LatestEventValue, With};
358    use crate::{
359        client::WeakClient,
360        room::WeakRoom,
361        send_queue::{LocalEcho, LocalEchoContent, RoomSendQueue, RoomSendQueueUpdate, SendHandle},
362        test_utils::mocks::MatrixMockServer,
363    };
364
365    fn new_local_echo_content(
366        room_send_queue: &RoomSendQueue,
367        transaction_id: &OwnedTransactionId,
368        body: &str,
369    ) -> LocalEchoContent {
370        LocalEchoContent::Event {
371            serialized_event: SerializableEventContent::new(
372                &AnyMessageLikeEventContent::RoomMessage(RoomMessageEventContent::text_plain(body)),
373            )
374            .unwrap(),
375            send_handle: SendHandle::new(
376                room_send_queue.clone(),
377                transaction_id.clone(),
378                MilliSecondsSinceUnixEpoch::now(),
379            ),
380            send_error: None,
381        }
382    }
383
384    #[async_test]
385    async fn test_new_loads_from_room_info() {
386        let room_id = room_id!("!r0");
387
388        let server = MatrixMockServer::new().await;
389        let client = server.client_builder().build().await;
390        let weak_client = WeakClient::from_client(&client);
391
392        // Create the room.
393        let room = client.base_client().get_or_create_room(room_id, RoomState::Joined);
394        let weak_room = WeakRoom::new(weak_client, room_id.to_owned());
395
396        // First time `LatestEvent` is created: we get `None`.
397        {
398            let (latest_event, is_none) = With::unzip(LatestEvent::new(&weak_room, None));
399
400            // By default, it's `None`.
401            assert_matches!(latest_event.current_value.get().await, LatestEventValue::None);
402            assert!(is_none);
403        }
404
405        // Set the `RoomInfo`.
406        {
407            room.update_room_info(|mut info| {
408                info.set_latest_event(LatestEventValue::LocalIsSending(local_room_message("foo")));
409                (info, Default::default())
410            })
411            .await;
412        }
413
414        // Second time. We get `LocalIsSending` from `RoomInfo`.
415        {
416            let (latest_event, is_none) = With::unzip(LatestEvent::new(&weak_room, None));
417
418            // By default, it's `None`.
419            assert_matches!(
420                latest_event.current_value.get().await,
421                LatestEventValue::LocalIsSending(_)
422            );
423            assert!(is_none.not());
424        }
425    }
426
427    #[async_test]
428    async fn test_update_do_not_ignore_none_value() {
429        let room_id = room_id!("!r0");
430
431        let server = MatrixMockServer::new().await;
432        let client = server.client_builder().build().await;
433        let weak_client = WeakClient::from_client(&client);
434
435        // Create the room.
436        client.base_client().get_or_create_room(room_id, RoomState::Joined);
437        let weak_room = WeakRoom::new(weak_client, room_id.to_owned());
438
439        // Get a `RoomEventCache`.
440        let event_cache = client.event_cache();
441        event_cache.subscribe().unwrap();
442
443        let mut latest_event = LatestEvent::new(&weak_room, None);
444
445        // First off, check the default value is `None`!
446        assert_matches!(latest_event.current_value.get().await, LatestEventValue::None);
447
448        // Second, set a new value.
449        latest_event.update(LatestEventValue::LocalIsSending(local_room_message("foo"))).await;
450
451        assert_matches!(
452            latest_event.current_value.get().await,
453            LatestEventValue::LocalIsSending(_)
454        );
455
456        // Finally, set a new `None` value. It must NOT be ignored.
457        latest_event.update(LatestEventValue::None).await;
458
459        assert_matches!(latest_event.current_value.get().await, LatestEventValue::None);
460    }
461
462    #[async_test]
463    async fn test_update_ignore_none_if_previous_value_is_none() {
464        let room_id = room_id!("!r0");
465
466        let server = MatrixMockServer::new().await;
467        let client = server.client_builder().build().await;
468        let weak_client = WeakClient::from_client(&client);
469
470        // Create the room.
471        client.base_client().get_or_create_room(room_id, RoomState::Joined);
472        let weak_room = WeakRoom::new(weak_client, room_id.to_owned());
473
474        let mut latest_event = LatestEvent::new(&weak_room, None);
475
476        let mut stream = latest_event.subscribe().await;
477        assert_pending!(stream);
478
479        // Set a non-`None` value.
480        latest_event.update(LatestEventValue::LocalIsSending(local_room_message("foo"))).await;
481        // We get it.
482        assert_next_matches!(stream, LatestEventValue::LocalIsSending(_));
483
484        // Set a `None` value.
485        latest_event.update(LatestEventValue::None).await;
486        // We get it.
487        assert_next_matches!(stream, LatestEventValue::None);
488
489        // Set a `None` value, again!
490        latest_event.update(LatestEventValue::None).await;
491        // We get it? No!
492        assert_pending!(stream);
493
494        // Set a `None` value, again, and again!
495        latest_event.update(LatestEventValue::None).await;
496        // No means No!
497        assert_pending!(stream);
498
499        // Set a non-`None` value.
500        latest_event.update(LatestEventValue::LocalIsSending(local_room_message("bar"))).await;
501        // We get it. Oof.
502        assert_next_matches!(stream, LatestEventValue::LocalIsSending(_));
503
504        assert_pending!(stream);
505    }
506
507    #[async_test]
508    async fn test_updates_do_not_ignore_local_values() {
509        let room_id = room_id!("!r0");
510
511        let server = MatrixMockServer::new().await;
512        let client = server.client_builder().build().await;
513        let weak_client = WeakClient::from_client(&client);
514
515        client.base_client().get_or_create_room(room_id, RoomState::Joined);
516        let weak_room = WeakRoom::new(weak_client, room_id.to_owned());
517
518        let mut latest_event = LatestEvent::new(&weak_room, None);
519
520        let mut stream = latest_event.subscribe().await;
521        assert_pending!(stream);
522
523        // A local event is being sent.
524        latest_event.update(LatestEventValue::LocalIsSending(local_room_message("foo"))).await;
525        assert_next_matches!(stream, LatestEventValue::LocalIsSending(_));
526
527        // A second local event is not ignored
528        latest_event.update(LatestEventValue::LocalIsSending(local_room_message("bar"))).await;
529        assert_next_matches!(stream, LatestEventValue::LocalIsSending(_));
530
531        // The send queue is wedged but even that shouldn't be ignored.
532        latest_event.update(LatestEventValue::LocalCannotBeSent(local_room_message("bar"))).await;
533        assert_next_matches!(stream, LatestEventValue::LocalCannotBeSent(_));
534
535        // Not when it's retrying either
536        latest_event.update(LatestEventValue::LocalIsSending(local_room_message("bar"))).await;
537        assert_next_matches!(stream, LatestEventValue::LocalIsSending(_));
538
539        assert_pending!(stream);
540    }
541
542    #[async_test]
543    async fn test_update_does_not_ignore_a_new_value_that_has_no_event_id() {
544        let room_id = room_id!("!r0");
545
546        let server = MatrixMockServer::new().await;
547        let client = server.client_builder().build().await;
548        let weak_client = WeakClient::from_client(&client);
549
550        client.base_client().get_or_create_room(room_id, RoomState::Joined);
551        let weak_room = WeakRoom::new(weak_client, room_id.to_owned());
552
553        let mut latest_event = LatestEvent::new(&weak_room, None);
554
555        let mut stream = latest_event.subscribe().await;
556        assert_pending!(stream);
557
558        // A local event is being sent: it has no event ID.
559        latest_event.update(LatestEventValue::LocalIsSending(local_room_message("foo"))).await;
560        assert_next_matches!(stream, LatestEventValue::LocalIsSending(_));
561
562        // An invite computed from stripped state events has no event ID either, but
563        // both values are obviously different: this must not be ignored.
564        latest_event
565            .update(LatestEventValue::RemoteInvite {
566                event_id: None,
567                timestamp: MilliSecondsSinceUnixEpoch::now(),
568                inviter: Some(owned_user_id!("@mnt_io:matrix.org")),
569            })
570            .await;
571        assert_next_matches!(stream, LatestEventValue::RemoteInvite { .. });
572
573        assert_pending!(stream);
574    }
575
576    #[async_test]
577    async fn test_update_ignore_when_previous_value_has_the_same_event_id() {
578        let room_id = room_id!("!r0");
579        let user_id = user_id!("@mnt_io:matrix.org");
580        let event_factory = EventFactory::new().sender(user_id).room(room_id);
581
582        let server = MatrixMockServer::new().await;
583        let client = server.client_builder().build().await;
584        let weak_client = WeakClient::from_client(&client);
585
586        // Create the room.
587        client.base_client().get_or_create_room(room_id, RoomState::Joined);
588        let weak_room = WeakRoom::new(weak_client, room_id.to_owned());
589
590        let mut latest_event = LatestEvent::new(&weak_room, None);
591
592        let mut stream = latest_event.subscribe().await;
593        assert_pending!(stream);
594
595        // Set a non-`None` value.
596        latest_event.update(LatestEventValue::LocalIsSending(local_room_message("foo"))).await;
597        // We get it.
598        assert_next_matches!(stream, LatestEventValue::LocalIsSending(_));
599
600        // Set a non-`None` value, with a specific event ID.
601        let first_event: RemoteLatestEventValue =
602            event_factory.text_msg("A").event_id(event_id!("$ev0")).into();
603        latest_event.update(LatestEventValue::Remote(first_event.clone())).await;
604        // We get it.
605        assert_next_matches!(stream, LatestEventValue::Remote(_));
606
607        // Set a non-`None` value again, with the same event ID!
608        latest_event.update(LatestEventValue::Remote(first_event)).await;
609        // It's ignored!
610        assert_pending!(stream);
611
612        // Set a non-`None` value again, with a different event ID!
613        let second_event = event_factory.text_msg("A").event_id(event_id!("$ev1")).into();
614        latest_event.update(LatestEventValue::Remote(second_event)).await;
615        // We get it!
616        assert_next_matches!(stream, LatestEventValue::Remote(_));
617
618        assert_pending!(stream);
619    }
620
621    #[async_test]
622    async fn test_local_has_priority_over_remote() {
623        let room_id = owned_room_id!("!r0");
624        let user_id = user_id!("@mnt_io:matrix.org");
625        let event_factory = EventFactory::new().sender(user_id).room(&room_id);
626
627        let server = MatrixMockServer::new().await;
628        let client = server.client_builder().build().await;
629        client.base_client().get_or_create_room(&room_id, RoomState::Joined);
630        let room = client.get_room(&room_id).unwrap();
631        let weak_room = WeakRoom::new(WeakClient::from_client(&client), room_id.clone());
632
633        let event_cache = client.event_cache();
634        event_cache.subscribe().unwrap();
635
636        // Fill the event cache with one event.
637        client
638            .event_cache_store()
639            .lock()
640            .await
641            .expect("Could not acquire the event cache lock")
642            .as_clean()
643            .expect("Could not acquire a clean event cache lock")
644            .handle_linked_chunk_updates(
645                LinkedChunkId::Room(&room_id),
646                vec![
647                    Update::NewItemsChunk {
648                        previous: None,
649                        new: ChunkIdentifier::new(0),
650                        next: None,
651                    },
652                    Update::PushItems {
653                        at: Position::new(ChunkIdentifier::new(0), 0),
654                        items: vec![event_factory.text_msg("A").event_id(event_id!("$ev0")).into()],
655                    },
656                ],
657            )
658            .await
659            .unwrap();
660
661        let (room_event_cache, _) = event_cache.room(&room_id).await.unwrap();
662
663        let send_queue = client.send_queue();
664        let room_send_queue = send_queue.for_room(room);
665
666        let mut latest_event = LatestEvent::new(&weak_room, None);
667
668        // First, let's create a `LatestEventValue` from the event cache. It must work.
669        {
670            latest_event.update_with_event_cache(&room_event_cache, user_id, None).await;
671
672            assert_matches!(latest_event.current_value.get().await, LatestEventValue::Remote(_));
673        }
674
675        // Second, let's create a `LatestEventValue` from the send queue. It
676        // must overwrite the current `LatestEventValue`.
677        let transaction_id = OwnedTransactionId::from("txnid0");
678
679        {
680            let content = new_local_echo_content(&room_send_queue, &transaction_id, "B");
681
682            let update = RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
683                transaction_id: transaction_id.clone(),
684                content,
685            });
686
687            latest_event.update_with_send_queue(&update, &room_event_cache, user_id, None).await;
688
689            assert_matches!(
690                latest_event.current_value.get().await,
691                LatestEventValue::LocalIsSending(_)
692            );
693        }
694
695        // Third, let's create a `LatestEventValue` from the event cache.
696        // Nothing must happen, it cannot overwrite the current
697        // `LatestEventValue` because the local event isn't sent yet.
698        {
699            latest_event.update_with_event_cache(&room_event_cache, user_id, None).await;
700
701            assert_matches!(
702                latest_event.current_value.get().await,
703                LatestEventValue::LocalIsSending(_)
704            );
705        }
706
707        // Fourth, let's a `LatestEventValue` from the send queue. It must stay the
708        // same, but now the local event is sent.
709        {
710            let update = RoomSendQueueUpdate::SentEvent {
711                transaction_id,
712                event_id: owned_event_id!("$ev1"),
713            };
714
715            latest_event.update_with_send_queue(&update, &room_event_cache, user_id, None).await;
716
717            assert_matches!(
718                latest_event.current_value.get().await,
719                LatestEventValue::LocalHasBeenSent { .. }
720            );
721        }
722
723        // Finally, let's create a `LatestEventValue` from the event cache. _Now_ it's
724        // possible, because there is no more local events.
725        {
726            latest_event.update_with_event_cache(&room_event_cache, user_id, None).await;
727
728            assert_matches!(latest_event.current_value.get().await, LatestEventValue::Remote(_));
729        }
730    }
731
732    #[async_test]
733    async fn test_redacted_latest_event_is_removed() {
734        let room_id = owned_room_id!("!r0");
735        let user_id = user_id!("@mnt_io:matrix.org");
736        let event_factory = EventFactory::new().sender(user_id).room(&room_id);
737
738        let server = MatrixMockServer::new().await;
739        let client = server.client_builder().build().await;
740        client.base_client().get_or_create_room(&room_id, RoomState::Joined);
741        let _room = client.get_room(&room_id).unwrap();
742        let weak_room = WeakRoom::new(WeakClient::from_client(&client), room_id.clone());
743
744        let event_cache = client.event_cache();
745        event_cache.subscribe().unwrap();
746
747        let event_id_0 = event_id!("$ev0");
748        let event_id_1 = event_id!("$ev1");
749
750        // Fill the event cache with two events.
751        client
752            .event_cache_store()
753            .lock()
754            .await
755            .expect("Could not acquire the event cache lock")
756            .as_clean()
757            .expect("Could not acquire a clean event cache lock")
758            .handle_linked_chunk_updates(
759                LinkedChunkId::Room(&room_id),
760                vec![
761                    Update::NewItemsChunk {
762                        previous: None,
763                        new: ChunkIdentifier::new(0),
764                        next: None,
765                    },
766                    Update::PushItems {
767                        at: Position::new(ChunkIdentifier::new(0), 0),
768                        items: vec![
769                            event_factory.text_msg("A").event_id(event_id_0).into(),
770                            event_factory.text_msg("B").event_id(event_id_1).into(),
771                        ],
772                    },
773                ],
774            )
775            .await
776            .unwrap();
777
778        let (room_event_cache, _) = event_cache.room(&room_id).await.unwrap();
779
780        let mut latest_event = LatestEvent::new(&weak_room, None);
781
782        // Let's create a `LatestEventValue` from the event cache. It must work.
783        {
784            latest_event.update_with_event_cache(&room_event_cache, user_id, None).await;
785
786            assert_matches!(
787                latest_event.current_value.get().await,
788                LatestEventValue::Remote(remote) => {
789                    assert_eq!(remote.event_id(), Some(event_id_1));
790                }
791            );
792        }
793
794        // Now, let's redact `$ev1`.
795        {
796            server
797                .mock_sync()
798                .ok_and_run(&client, |builder| {
799                    builder.add_joined_room(
800                        JoinedRoomBuilder::new(&room_id)
801                            .add_timeline_event(event_factory.redaction(event_id_1)),
802                    );
803                })
804                .await;
805
806            yield_now().await;
807
808            latest_event.update_with_event_cache(&room_event_cache, user_id, None).await;
809
810            assert_matches!(
811                latest_event.current_value.get().await,
812                LatestEventValue::Remote(remote) => {
813                    // `$ev1` has been redacted, so `$ev0` is the new candidate!
814                    assert_eq!(remote.event_id(), Some(event_id_0));
815                }
816            );
817        }
818    }
819
820    #[async_test]
821    async fn test_store_latest_event_value() {
822        let room_id = owned_room_id!("!r0");
823        let user_id = user_id!("@mnt_io:matrix.org");
824        let event_factory = EventFactory::new().sender(user_id).room(&room_id);
825
826        let server = MatrixMockServer::new().await;
827
828        let store_config =
829            StoreConfig::new(CrossProcessLockConfig::multi_process("cross-process-lock-holder"));
830
831        // Load the client for the first time, and run some operations.
832        {
833            let client = server
834                .client_builder()
835                .on_builder(|builder| builder.store_config(store_config.clone()))
836                .build()
837                .await;
838            let mut room_info_notable_update_receiver = client.room_info_notable_update_receiver();
839            let room = client.base_client().get_or_create_room(&room_id, RoomState::Joined);
840            let weak_room = WeakRoom::new(WeakClient::from_client(&client), room_id.clone());
841
842            let event_cache = client.event_cache();
843            event_cache.subscribe().unwrap();
844
845            // Fill the event cache with one event.
846            client
847                .event_cache_store()
848                .lock()
849                .await
850                .expect("Could not acquire the event cache lock")
851                .as_clean()
852                .expect("Could not acquire a clean event cache lock")
853                .handle_linked_chunk_updates(
854                    LinkedChunkId::Room(&room_id),
855                    vec![
856                        Update::NewItemsChunk {
857                            previous: None,
858                            new: ChunkIdentifier::new(0),
859                            next: None,
860                        },
861                        Update::PushItems {
862                            at: Position::new(ChunkIdentifier::new(0), 0),
863                            items: vec![
864                                event_factory.text_msg("A").event_id(event_id!("$ev0")).into(),
865                            ],
866                        },
867                    ],
868                )
869                .await
870                .unwrap();
871
872            let (room_event_cache, _) = event_cache.room(&room_id).await.unwrap();
873
874            // Check there is no `LatestEventValue` for the moment.
875            {
876                let latest_event = room.latest_event();
877
878                assert_matches!(latest_event, LatestEventValue::None);
879            }
880
881            // Generate a new `LatestEventValue`.
882            {
883                let mut latest_event = LatestEvent::new(&weak_room, None);
884                latest_event.update_with_event_cache(&room_event_cache, user_id, None).await;
885
886                assert_matches!(
887                    latest_event.current_value.get().await,
888                    LatestEventValue::Remote(_)
889                );
890            }
891
892            // We see the `RoomInfoNotableUpdateReasons`.
893            {
894                let update = room_info_notable_update_receiver.recv().await.unwrap();
895
896                assert_eq!(update.room_id, room_id);
897                assert!(update.reasons.contains(RoomInfoNotableUpdateReasons::LATEST_EVENT));
898            }
899
900            // Check it's in the `RoomInfo` and in `Room`.
901            {
902                let latest_event = room.latest_event();
903
904                assert_matches!(latest_event, LatestEventValue::Remote(_));
905            }
906        }
907
908        // Reload the client with the same store config, and see the `LatestEventValue`
909        // is inside the `RoomInfo`.
910        {
911            let client = server
912                .client_builder()
913                .on_builder(|builder| builder.store_config(store_config))
914                .build()
915                .await;
916            let room = client.get_room(&room_id).unwrap();
917            let latest_event = room.latest_event();
918
919            assert_matches!(latest_event, LatestEventValue::Remote(_));
920        }
921    }
922}