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 state: LatestEventValueLocalState,
74 },
75}
76
77#[derive(Debug)]
78#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
79pub enum LatestEventValueLocalState {
80 IsSending,
81 HasBeenSent,
82 CannotBeSent,
83}
84
85impl LatestEventValue {
86 pub(crate) async fn from_base_latest_event_value(
87 value: BaseLatestEventValue,
88 room: &Room,
89 client: &Client,
90 ) -> Self {
91 match value {
92 BaseLatestEventValue::None => Self::None,
93 BaseLatestEventValue::Remote(timeline_event) => {
94 let raw_any_sync_timeline_event = timeline_event.into_raw();
95 let Ok(any_sync_timeline_event) = raw_any_sync_timeline_event.deserialize() else {
96 return Self::None;
97 };
98
99 let timestamp = any_sync_timeline_event.origin_server_ts();
100 let sender = any_sync_timeline_event.sender().to_owned();
101 let is_own = client.user_id().map(|user_id| user_id == sender).unwrap_or(false);
102 let profile = room
103 .profile_from_user_id(&sender)
104 .await
105 .map(TimelineDetails::Ready)
106 .unwrap_or(TimelineDetails::Unavailable);
107
108 match TimelineAction::from_event(
109 any_sync_timeline_event,
110 &raw_any_sync_timeline_event,
111 room,
112 None,
113 None,
114 None,
115 None,
116 )
117 .await
118 {
119 Some(TimelineAction::AddItem { content }) => {
121 Self::Remote { timestamp, sender, is_own, profile, content }
122 }
123
124 Some(TimelineAction::HandleAggregation {
128 kind:
129 HandleAggregationKind::Edit { replacement: Replacement { new_content, .. } },
130 ..
131 }) => {
132 match TimelineAction::from_content(
134 AnyMessageLikeEventContent::RoomMessage(RoomMessageEventContent::new(
135 new_content.msgtype,
136 )),
137 None,
140 None,
143 None,
144 ) {
145 TimelineAction::AddItem { content } => {
147 Self::Remote { timestamp, sender, is_own, profile, content }
148 }
149
150 _ => {
153 trace!("latest event was an edit that failed to be un-aggregated");
154
155 Self::None
156 }
157 }
158 }
159
160 _ => Self::None,
161 }
162 }
163 BaseLatestEventValue::LocalIsSending(ref local_value)
164 | BaseLatestEventValue::LocalHasBeenSent { value: ref local_value, .. }
165 | BaseLatestEventValue::LocalCannotBeSent(ref local_value) => {
166 let LocalLatestEventValue { timestamp, content: serialized_content } = local_value;
167
168 let Ok(message_like_event_content) = serialized_content.deserialize() else {
169 return Self::None;
170 };
171
172 let sender =
173 client.user_id().expect("The `Client` is supposed to be logged").to_owned();
174 let profile = room
175 .profile_from_user_id(&sender)
176 .await
177 .map(TimelineDetails::Ready)
178 .unwrap_or(TimelineDetails::Unavailable);
179
180 match TimelineAction::from_content(message_like_event_content, None, None, None) {
181 TimelineAction::AddItem { content } => Self::Local {
182 timestamp: *timestamp,
183 sender,
184 profile,
185 content,
186 state: match value {
187 BaseLatestEventValue::LocalIsSending(_) => {
188 LatestEventValueLocalState::IsSending
189 }
190 BaseLatestEventValue::LocalHasBeenSent { .. } => {
191 LatestEventValueLocalState::HasBeenSent
192 }
193 BaseLatestEventValue::LocalCannotBeSent(_) => {
194 LatestEventValueLocalState::CannotBeSent
195 }
196 BaseLatestEventValue::Remote(_) | BaseLatestEventValue::None => {
197 unreachable!("Only local latest events are supposed to be handled");
198 }
199 },
200 },
201
202 TimelineAction::HandleAggregation { kind, .. } => {
203 trace!("latest event is an aggregation: {}", kind.debug_string());
206 Self::None
207 }
208 }
209 }
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use std::ops::Not;
217
218 use assert_matches::assert_matches;
219 use matrix_sdk::{
220 latest_events::{LocalLatestEventValue, RemoteLatestEventValue},
221 store::SerializableEventContent,
222 test_utils::mocks::MatrixMockServer,
223 };
224 use matrix_sdk_test::{JoinedRoomBuilder, async_test, event_factory::EventFactory};
225 use ruma::{
226 MilliSecondsSinceUnixEpoch, event_id,
227 events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
228 room_id, uint, user_id,
229 };
230
231 use super::{
232 super::{MsgLikeContent, MsgLikeKind, TimelineItemContent},
233 BaseLatestEventValue, LatestEventValue, LatestEventValueLocalState, TimelineDetails,
234 };
235
236 #[async_test]
237 async fn test_none() {
238 let server = MatrixMockServer::new().await;
239 let client = server.client_builder().build().await;
240 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
241
242 let base_value = BaseLatestEventValue::None;
243 let value =
244 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
245
246 assert_matches!(value, LatestEventValue::None);
247 }
248
249 #[async_test]
250 async fn test_remote() {
251 let server = MatrixMockServer::new().await;
252 let client = server.client_builder().build().await;
253 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
254 let sender = user_id!("@mnt_io:matrix.org");
255 let event_factory = EventFactory::new();
256
257 let base_value = BaseLatestEventValue::Remote(RemoteLatestEventValue::from_plaintext(
258 event_factory
259 .server_ts(42)
260 .sender(sender)
261 .text_msg("raclette")
262 .event_id(event_id!("$ev0"))
263 .into_raw_sync(),
264 ));
265 let value =
266 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
267
268 assert_matches!(value, LatestEventValue::Remote { timestamp, sender: received_sender, is_own, profile, content } => {
269 assert_eq!(u64::from(timestamp.get()), 42u64);
270 assert_eq!(received_sender, sender);
271 assert!(is_own.not());
272 assert_matches!(profile, TimelineDetails::Unavailable);
273 assert_matches!(
274 content,
275 TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(message), .. }) => {
276 assert_eq!(message.body(), "raclette");
277 }
278 );
279 })
280 }
281
282 #[async_test]
283 async fn test_local_is_sending() {
284 let server = MatrixMockServer::new().await;
285 let client = server.client_builder().build().await;
286 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
287
288 let base_value = BaseLatestEventValue::LocalIsSending(LocalLatestEventValue {
289 timestamp: MilliSecondsSinceUnixEpoch(uint!(42)),
290 content: SerializableEventContent::new(&AnyMessageLikeEventContent::RoomMessage(
291 RoomMessageEventContent::text_plain("raclette"),
292 ))
293 .unwrap(),
294 });
295 let value =
296 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
297
298 assert_matches!(value, LatestEventValue::Local { timestamp, sender, profile, content, state } => {
299 assert_eq!(u64::from(timestamp.get()), 42u64);
300 assert_eq!(sender, "@example:localhost");
301 assert_matches!(profile, TimelineDetails::Unavailable);
302 assert_matches!(
303 content,
304 TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. })
305 );
306 assert_matches!(state, LatestEventValueLocalState::IsSending);
307 })
308 }
309
310 #[async_test]
311 async fn test_local_has_been_sent() {
312 let server = MatrixMockServer::new().await;
313 let client = server.client_builder().build().await;
314 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
315
316 let base_value = BaseLatestEventValue::LocalHasBeenSent {
317 event_id: event_id!("$ev0").to_owned(),
318 value: LocalLatestEventValue {
319 timestamp: MilliSecondsSinceUnixEpoch(uint!(42)),
320 content: SerializableEventContent::new(&AnyMessageLikeEventContent::RoomMessage(
321 RoomMessageEventContent::text_plain("raclette"),
322 ))
323 .unwrap(),
324 },
325 };
326 let value =
327 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
328
329 assert_matches!(value, LatestEventValue::Local { timestamp, sender, profile, content, state } => {
330 assert_eq!(u64::from(timestamp.get()), 42u64);
331 assert_eq!(sender, "@example:localhost");
332 assert_matches!(profile, TimelineDetails::Unavailable);
333 assert_matches!(
334 content,
335 TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. })
336 );
337 assert_matches!(state, LatestEventValueLocalState::HasBeenSent);
338 })
339 }
340
341 #[async_test]
342 async fn test_local_cannot_be_sent() {
343 let server = MatrixMockServer::new().await;
344 let client = server.client_builder().build().await;
345 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
346
347 let base_value = BaseLatestEventValue::LocalCannotBeSent(LocalLatestEventValue {
348 timestamp: MilliSecondsSinceUnixEpoch(uint!(42)),
349 content: SerializableEventContent::new(&AnyMessageLikeEventContent::RoomMessage(
350 RoomMessageEventContent::text_plain("raclette"),
351 ))
352 .unwrap(),
353 });
354 let value =
355 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
356
357 assert_matches!(value, LatestEventValue::Local { timestamp, sender, profile, content, state } => {
358 assert_eq!(u64::from(timestamp.get()), 42u64);
359 assert_eq!(sender, "@example:localhost");
360 assert_matches!(profile, TimelineDetails::Unavailable);
361 assert_matches!(
362 content,
363 TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. })
364 );
365 assert_matches!(state, LatestEventValueLocalState::CannotBeSent);
366 })
367 }
368
369 #[async_test]
370 async fn test_remote_edit() {
371 let server = MatrixMockServer::new().await;
372 let client = server.client_builder().build().await;
373 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
374 let sender = user_id!("@mnt_io:matrix.org");
375 let event_factory = EventFactory::new();
376
377 let base_value = BaseLatestEventValue::Remote(RemoteLatestEventValue::from_plaintext(
378 event_factory
379 .server_ts(42)
380 .sender(sender)
381 .text_msg("bonjour")
382 .event_id(event_id!("$ev1"))
383 .edit(event_id!("$ev0"), RoomMessageEventContent::text_plain("fondue").into())
384 .into_raw_sync(),
385 ));
386 let value =
387 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
388
389 assert_matches!(value, LatestEventValue::Remote { timestamp, sender: received_sender, is_own, profile, content } => {
390 assert_eq!(u64::from(timestamp.get()), 42u64);
391 assert_eq!(received_sender, sender);
392 assert!(is_own.not());
393 assert_matches!(profile, TimelineDetails::Unavailable);
394 assert_matches!(
395 content,
396 TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(message), .. }) => {
397 assert_eq!(message.body(), "fondue");
398 }
399 );
400 })
401 }
402}