diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 799e970cb..e69e38b4b 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -24,8 +24,6 @@ use bitflags::bitflags; use eyeball::{SharedObservable, Subscriber}; #[cfg(all(feature = "e2e-encryption", feature = "experimental-sliding-sync"))] use matrix_sdk_common::ring_buffer::RingBuffer; -#[cfg(feature = "experimental-sliding-sync")] -use ruma::events::AnySyncTimelineEvent; use ruma::{ api::client::sync::sync_events::v3::RoomSummary as RumaSummary, events::{ @@ -51,6 +49,8 @@ use ruma::{ EventId, MxcUri, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId, }; +#[cfg(feature = "experimental-sliding-sync")] +use ruma::{events::AnySyncTimelineEvent, MilliSecondsSinceUnixEpoch}; use serde::{Deserialize, Serialize}; use tokio::sync::broadcast; use tracing::{debug, field::debug, info, instrument, warn}; @@ -888,6 +888,14 @@ impl Room { pub fn is_marked_unread(&self) -> bool { self.inner.read().base_info.is_marked_unread } + + /// Returns the recency timestamp of the room. + /// + /// Please read `RoomInfo::recency_timestamp` to learn more. + #[cfg(feature = "experimental-sliding-sync")] + pub fn recency_timestamp(&self) -> Option { + self.inner.read().recency_timestamp + } } /// The underlying pure data structure for joined and left rooms. @@ -946,6 +954,16 @@ pub struct RoomInfo { /// filled at start when creating a room, or on every successful sync. #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) cached_display_name: Option, + + /// The recency timestamp of this room. + /// + /// It's not to be confused with `origin_server_ts` of the latest event. + /// Sliding Sync might "ignore” some events when computing the recency + /// timestamp of the room. Thus, using this `recency_timestamp` value is + /// more accurate than relying on the latest event. + #[cfg(feature = "experimental-sliding-sync")] + #[serde(default)] + pub(crate) recency_timestamp: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -983,6 +1001,8 @@ impl RoomInfo { base_info: Box::new(BaseRoomInfo::new()), warned_about_unknown_room_version: Arc::new(false.into()), cached_display_name: None, + #[cfg(feature = "experimental-sliding-sync")] + recency_timestamp: None, } } @@ -1387,6 +1407,14 @@ impl RoomInfo { pub fn latest_event(&self) -> Option<&LatestEvent> { self.latest_event.as_deref() } + + /// Updates the recency timestamp of this room. + /// + /// Please read [`Self::recency_timestamp`] to learn more. + #[cfg(feature = "experimental-sliding-sync")] + pub(crate) fn update_recency_timestamp(&mut self, timestamp: MilliSecondsSinceUnixEpoch) { + self.recency_timestamp = Some(timestamp); + } } #[cfg(feature = "experimental-sliding-sync")] @@ -1601,6 +1629,7 @@ mod tests { read_receipts: Default::default(), warned_about_unknown_room_version: Arc::new(false.into()), cached_display_name: None, + recency_timestamp: Some(MilliSecondsSinceUnixEpoch(42u32.into())), }; let info_json = json!({ @@ -1653,6 +1682,7 @@ mod tests { "latest_active": null, "pending": [] }, + "recency_timestamp": 42, }); assert_eq!(serde_json::to_value(info).unwrap(), info_json); diff --git a/crates/matrix-sdk-base/src/sliding_sync.rs b/crates/matrix-sdk-base/src/sliding_sync.rs index 2cd0dbe8a..3bca1564b 100644 --- a/crates/matrix-sdk-base/src/sliding_sync.rs +++ b/crates/matrix-sdk-base/src/sliding_sync.rs @@ -733,6 +733,10 @@ fn process_room_properties(room_data: &v4::SlidingSyncRoom, room_info: &mut Room if room_data.limited { room_info.mark_members_missing(); } + + if let Some(recency_timestamp) = &room_data.timestamp { + room_info.update_recency_timestamp(*recency_timestamp); + } } #[cfg(test)] @@ -762,7 +766,8 @@ mod tests { }, mxc_uri, owned_mxc_uri, owned_user_id, room_alias_id, room_id, serde::Raw, - uint, user_id, JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId, + uint, user_id, JsOption, MilliSecondsSinceUnixEpoch, MxcUri, OwnedRoomId, OwnedUserId, + RoomAliasId, RoomId, UserId, }; use serde_json::json; @@ -1736,6 +1741,80 @@ mod tests { assert_eq!(rawev_id(room.latest_event().unwrap().event().clone()), "$a"); } + #[async_test] + async fn test_recency_timestamp_is_found_when_processing_sliding_sync_response() { + // Given a logged-in client + let client = logged_in_base_client(None).await; + let room_id = room_id!("!r:e.uk"); + + // When I send sliding sync response containing a room with a recency timestamp + let room = assign!(v4::SlidingSyncRoom::new(), { + timestamp: Some(MilliSecondsSinceUnixEpoch(42u32.into())), + }); + let response = response_with_room(room_id, room); + client.process_sliding_sync(&response, &()).await.expect("Failed to process sync"); + + // Then the room in the client has the recency timestamp + let client_room = client.get_room(room_id).expect("No room found"); + assert_eq!(client_room.recency_timestamp().expect("No recency timestamp").0, 42u32.into()); + } + + #[async_test] + async fn test_recency_timestamp_can_be_overwritten_when_present_in_a_sliding_sync_response() { + // Given a logged-in client + let client = logged_in_base_client(None).await; + let room_id = room_id!("!r:e.uk"); + + { + // When I send sliding sync response containing a room with a recency timestamp + let room = assign!(v4::SlidingSyncRoom::new(), { + timestamp: Some(MilliSecondsSinceUnixEpoch(42u32.into())), + }); + let response = response_with_room(room_id, room); + client.process_sliding_sync(&response, &()).await.expect("Failed to process sync"); + + // Then the room in the client has the recency timestamp + let client_room = client.get_room(room_id).expect("No room found"); + assert_eq!( + client_room.recency_timestamp().expect("No recency timestamp").0, + 42u32.into() + ); + } + + { + // When I send sliding sync response containing a room with NO recency timestamp + let room = assign!(v4::SlidingSyncRoom::new(), { + timestamp: None, + }); + let response = response_with_room(room_id, room); + client.process_sliding_sync(&response, &()).await.expect("Failed to process sync"); + + // Then the room in the client has the previous recency timestamp + let client_room = client.get_room(room_id).expect("No room found"); + assert_eq!( + client_room.recency_timestamp().expect("No recency timestamp").0, + 42u32.into() + ); + } + + { + // When I send sliding sync response containing a room with a NEW recency + // timestamp + let room = assign!(v4::SlidingSyncRoom::new(), { + timestamp: Some(MilliSecondsSinceUnixEpoch(153u32.into())), + }); + let response = response_with_room(room_id, room); + client.process_sliding_sync(&response, &()).await.expect("Failed to process sync"); + + // Then the room in the client has the recency timestamp + let client_room = client.get_room(room_id).expect("No room found"); + assert_eq!( + client_room.recency_timestamp().expect("No recency timestamp").0, + 153u32.into() + ); + } + } + async fn choose_event_to_cache(events: &[SyncTimelineEvent]) -> Option { let room = make_room(); let mut room_info = room.clone_info(); diff --git a/crates/matrix-sdk-base/src/store/migration_helpers.rs b/crates/matrix-sdk-base/src/store/migration_helpers.rs index 9621afce8..f4b611b40 100644 --- a/crates/matrix-sdk-base/src/store/migration_helpers.rs +++ b/crates/matrix-sdk-base/src/store/migration_helpers.rs @@ -125,6 +125,8 @@ impl RoomInfoV1 { base_info: base_info.migrate(create), warned_about_unknown_room_version: Arc::new(false.into()), cached_display_name: None, + #[cfg(feature = "experimental-sliding-sync")] + recency_timestamp: None, } } }