1use matrix_sdk::{Client, Room, latest_events::LocalLatestEventValue};
16use matrix_sdk_base::latest_event::LatestEventValue as BaseLatestEventValue;
17use ruma::{
18 MilliSecondsSinceUnixEpoch, OwnedUserId,
19 events::{
20 AnyMessageLikeEventContent, relation::Replacement, room::message::RoomMessageEventContent,
21 },
22};
23use tracing::trace;
24
25use crate::timeline::{
26 Profile, TimelineDetails, TimelineItemContent,
27 event_handler::{HandleAggregationKind, TimelineAction},
28 traits::RoomDataProvider,
29};
30
31#[derive(Debug)]
34pub enum LatestEventValue {
35 None,
37
38 Remote {
40 timestamp: MilliSecondsSinceUnixEpoch,
42
43 sender: OwnedUserId,
45
46 is_own: bool,
48
49 profile: TimelineDetails<Profile>,
51
52 content: TimelineItemContent,
54 },
55
56 Local {
60 timestamp: MilliSecondsSinceUnixEpoch,
62
63 sender: OwnedUserId,
65
66 profile: TimelineDetails<Profile>,
68
69 content: TimelineItemContent,
71
72 is_sending: bool,
75 },
76}
77
78impl LatestEventValue {
79 pub(crate) async fn from_base_latest_event_value(
80 value: BaseLatestEventValue,
81 room: &Room,
82 client: &Client,
83 ) -> Self {
84 match value {
85 BaseLatestEventValue::None => Self::None,
86 BaseLatestEventValue::Remote(timeline_event) => {
87 let raw_any_sync_timeline_event = timeline_event.into_raw();
88 let Ok(any_sync_timeline_event) = raw_any_sync_timeline_event.deserialize() else {
89 return Self::None;
90 };
91
92 let timestamp = any_sync_timeline_event.origin_server_ts();
93 let sender = any_sync_timeline_event.sender().to_owned();
94 let is_own = client.user_id().map(|user_id| user_id == sender).unwrap_or(false);
95 let profile = room
96 .profile_from_user_id(&sender)
97 .await
98 .map(TimelineDetails::Ready)
99 .unwrap_or(TimelineDetails::Unavailable);
100
101 match TimelineAction::from_event(
102 any_sync_timeline_event,
103 &raw_any_sync_timeline_event,
104 room,
105 None,
106 None,
107 None,
108 None,
109 )
110 .await
111 {
112 Some(TimelineAction::AddItem { content }) => {
114 Self::Remote { timestamp, sender, is_own, profile, content }
115 }
116
117 Some(TimelineAction::HandleAggregation {
121 kind:
122 HandleAggregationKind::Edit { replacement: Replacement { new_content, .. } },
123 ..
124 }) => {
125 match TimelineAction::from_content(
127 AnyMessageLikeEventContent::RoomMessage(RoomMessageEventContent::new(
128 new_content.msgtype,
129 )),
130 None,
133 None,
136 None,
137 ) {
138 TimelineAction::AddItem { content } => {
140 Self::Remote { timestamp, sender, is_own, profile, content }
141 }
142
143 _ => {
146 trace!("latest event was an edit that failed to be un-aggregated");
147
148 Self::None
149 }
150 }
151 }
152
153 _ => Self::None,
154 }
155 }
156 BaseLatestEventValue::LocalIsSending(LocalLatestEventValue {
157 timestamp,
158 content: ref serialized_content,
159 })
160 | BaseLatestEventValue::LocalCannotBeSent(LocalLatestEventValue {
161 timestamp,
162 content: ref serialized_content,
163 }) => {
164 let Ok(message_like_event_content) = serialized_content.deserialize() else {
165 return Self::None;
166 };
167
168 let sender =
169 client.user_id().expect("The `Client` is supposed to be logged").to_owned();
170 let profile = room
171 .profile_from_user_id(&sender)
172 .await
173 .map(TimelineDetails::Ready)
174 .unwrap_or(TimelineDetails::Unavailable);
175 let is_sending = matches!(value, BaseLatestEventValue::LocalIsSending(_));
176
177 match TimelineAction::from_content(message_like_event_content, None, None, None) {
178 TimelineAction::AddItem { content } => {
179 Self::Local { timestamp, sender, profile, content, is_sending }
180 }
181
182 TimelineAction::HandleAggregation { kind, .. } => {
183 trace!("latest event is an aggregation: {}", kind.debug_string());
186 Self::None
187 }
188 }
189 }
190 }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use std::ops::Not;
197
198 use assert_matches::assert_matches;
199 use matrix_sdk::{
200 latest_events::{LocalLatestEventValue, RemoteLatestEventValue},
201 store::SerializableEventContent,
202 test_utils::mocks::MatrixMockServer,
203 };
204 use matrix_sdk_test::{JoinedRoomBuilder, async_test, event_factory::EventFactory};
205 use ruma::{
206 MilliSecondsSinceUnixEpoch, event_id,
207 events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
208 room_id, uint, user_id,
209 };
210
211 use super::{
212 super::{MsgLikeContent, MsgLikeKind, TimelineItemContent},
213 BaseLatestEventValue, LatestEventValue, TimelineDetails,
214 };
215
216 #[async_test]
217 async fn test_none() {
218 let server = MatrixMockServer::new().await;
219 let client = server.client_builder().build().await;
220 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
221
222 let base_value = BaseLatestEventValue::None;
223 let value =
224 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
225
226 assert_matches!(value, LatestEventValue::None);
227 }
228
229 #[async_test]
230 async fn test_remote() {
231 let server = MatrixMockServer::new().await;
232 let client = server.client_builder().build().await;
233 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
234 let sender = user_id!("@mnt_io:matrix.org");
235 let event_factory = EventFactory::new();
236
237 let base_value = BaseLatestEventValue::Remote(RemoteLatestEventValue::from_plaintext(
238 event_factory
239 .server_ts(42)
240 .sender(sender)
241 .text_msg("raclette")
242 .event_id(event_id!("$ev0"))
243 .into_raw_sync(),
244 ));
245 let value =
246 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
247
248 assert_matches!(value, LatestEventValue::Remote { timestamp, sender: received_sender, is_own, profile, content } => {
249 assert_eq!(u64::from(timestamp.get()), 42u64);
250 assert_eq!(received_sender, sender);
251 assert!(is_own.not());
252 assert_matches!(profile, TimelineDetails::Unavailable);
253 assert_matches!(
254 content,
255 TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(message), .. }) => {
256 assert_eq!(message.body(), "raclette");
257 }
258 );
259 })
260 }
261
262 #[async_test]
263 async fn test_local_is_sending() {
264 let server = MatrixMockServer::new().await;
265 let client = server.client_builder().build().await;
266 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
267
268 let base_value = BaseLatestEventValue::LocalIsSending(LocalLatestEventValue {
269 timestamp: MilliSecondsSinceUnixEpoch(uint!(42)),
270 content: SerializableEventContent::new(&AnyMessageLikeEventContent::RoomMessage(
271 RoomMessageEventContent::text_plain("raclette"),
272 ))
273 .unwrap(),
274 });
275 let value =
276 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
277
278 assert_matches!(value, LatestEventValue::Local { timestamp, sender, profile, content, is_sending } => {
279 assert_eq!(u64::from(timestamp.get()), 42u64);
280 assert_eq!(sender, "@example:localhost");
281 assert_matches!(profile, TimelineDetails::Unavailable);
282 assert_matches!(
283 content,
284 TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. })
285 );
286 assert!(is_sending);
287 })
288 }
289
290 #[async_test]
291 async fn test_local_cannot_be_sent() {
292 let server = MatrixMockServer::new().await;
293 let client = server.client_builder().build().await;
294 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
295
296 let base_value = BaseLatestEventValue::LocalCannotBeSent(LocalLatestEventValue {
297 timestamp: MilliSecondsSinceUnixEpoch(uint!(42)),
298 content: SerializableEventContent::new(&AnyMessageLikeEventContent::RoomMessage(
299 RoomMessageEventContent::text_plain("raclette"),
300 ))
301 .unwrap(),
302 });
303 let value =
304 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
305
306 assert_matches!(value, LatestEventValue::Local { timestamp, sender, profile, content, is_sending } => {
307 assert_eq!(u64::from(timestamp.get()), 42u64);
308 assert_eq!(sender, "@example:localhost");
309 assert_matches!(profile, TimelineDetails::Unavailable);
310 assert_matches!(
311 content,
312 TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. })
313 );
314 assert!(is_sending.not());
315 })
316 }
317
318 #[async_test]
319 async fn test_remote_edit() {
320 let server = MatrixMockServer::new().await;
321 let client = server.client_builder().build().await;
322 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
323 let sender = user_id!("@mnt_io:matrix.org");
324 let event_factory = EventFactory::new();
325
326 let base_value = BaseLatestEventValue::Remote(RemoteLatestEventValue::from_plaintext(
327 event_factory
328 .server_ts(42)
329 .sender(sender)
330 .text_msg("bonjour")
331 .event_id(event_id!("$ev1"))
332 .edit(event_id!("$ev0"), RoomMessageEventContent::text_plain("fondue").into())
333 .into_raw_sync(),
334 ));
335 let value =
336 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
337
338 assert_matches!(value, LatestEventValue::Remote { timestamp, sender: received_sender, is_own, profile, content } => {
339 assert_eq!(u64::from(timestamp.get()), 42u64);
340 assert_eq!(received_sender, sender);
341 assert!(is_own.not());
342 assert_matches!(profile, TimelineDetails::Unavailable);
343 assert_matches!(
344 content,
345 TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(message), .. }) => {
346 assert_eq!(message.body(), "fondue");
347 }
348 );
349 })
350 }
351}