diff --git a/crates/matrix-sdk-search/src/index/mod.rs b/crates/matrix-sdk-search/src/index/mod.rs index 1576189a9..fe91ca3b7 100644 --- a/crates/matrix-sdk-search/src/index/mod.rs +++ b/crates/matrix-sdk-search/src/index/mod.rs @@ -49,7 +49,11 @@ pub struct IndexableEvent { /// The sender of the event. pub(crate) sender: OwnedUserId, /// The origin server timestamp of the event. - pub(crate) timestamp: MilliSecondsSinceUnixEpoch, + /// + /// Please use the `matrix_sdk_common::TimelineEvent::timestamp` as much as + /// possible as it protects against malformed `origin_server_ts`. At worst, + /// use the `matrix_sdk_common::serde_helpers::extract_timestamp` function. + pub(crate) timestamp: Option, /// The text to index for this event. pub(crate) body: String, } @@ -60,7 +64,7 @@ impl IndexableEvent { event_id: OwnedEventId, original_event_id: OwnedEventId, sender: OwnedUserId, - timestamp: MilliSecondsSinceUnixEpoch, + timestamp: Option, body: String, ) -> Self { Self { event_id, original_event_id, sender, timestamp, body } @@ -427,7 +431,7 @@ mod tests { event.event_id.clone(), original_event_id, event.sender.clone(), - event.origin_server_ts, + Some(event.origin_server_ts), content.body.clone(), ) } diff --git a/crates/matrix-sdk-search/src/schema.rs b/crates/matrix-sdk-search/src/schema.rs index 251d4d06a..5d6dbb370 100644 --- a/crates/matrix-sdk-search/src/schema.rs +++ b/crates/matrix-sdk-search/src/schema.rs @@ -96,11 +96,18 @@ impl MatrixSearchIndexSchema for RoomMessageSchema { /// Given an [`IndexableEvent`] return a [`TantivyDocument`]. fn make_doc(&self, event: IndexableEvent) -> Result { + let timestamp = DateTime::from_timestamp_millis( + event + .timestamp + .map(|timestamp| timestamp.get().into()) + // If the timestamp is missing, use 0 as the “no value”. + .unwrap_or(0), + ); + let document = doc!( self.event_id_field => event.event_id.to_string(), self.body_field => event.body, - self.date_field => - DateTime::from_timestamp_millis(event.timestamp.get().into()), + self.date_field => timestamp, self.sender_field => event.sender.to_string(), self.original_event_id_field => event.original_event_id.to_string(), ); diff --git a/crates/matrix-sdk/src/search_index/mod.rs b/crates/matrix-sdk/src/search_index/mod.rs index 203a3e84e..e22a3f4b8 100644 --- a/crates/matrix-sdk/src/search_index/mod.rs +++ b/crates/matrix-sdk/src/search_index/mod.rs @@ -27,7 +27,7 @@ use matrix_sdk_search::{ index::{IndexableEvent, RoomIndex, RoomIndexOperation, builder::RoomIndexBuilder}, }; use ruma::{ - EventId, OwnedEventId, OwnedRoomId, RoomId, + EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, RoomId, events::{ AnySyncMessageLikeEvent, AnySyncTimelineEvent, poll::{ @@ -284,7 +284,10 @@ fn room_message_body(msgtype: &MessageType) -> Option { /// Build an [`IndexableEvent`] from a room message, or `None` if its type /// carries no searchable text. -fn indexable_from_room_message(event: &OriginalSyncRoomMessageEvent) -> Option { +fn indexable_from_room_message( + event: &OriginalSyncRoomMessageEvent, + timestamp: Option, +) -> Option { let body = room_message_body(&event.content.msgtype)?; let original_event_id = match &event.content.relates_to { Some(Relation::Replacement(replacement)) => replacement.event_id.clone(), @@ -295,7 +298,7 @@ fn indexable_from_room_message(event: &OriginalSyncRoomMessageEvent) -> Option Option, cache: &RoomEventCache, ) -> Option { if let Some(Relation::Replacement(replacement_data)) = &event.content.relates_to { if let Some(recent) = get_most_recent_edit(cache, &replacement_data.event_id).await { return Some( - indexable_from_room_message(&recent).map_or( + indexable_from_room_message(&recent, timestamp).map_or( RoomIndexOperation::Noop, |indexable| { RoomIndexOperation::Edit(replacement_data.event_id.clone(), indexable) @@ -328,15 +332,18 @@ async fn handle_possible_edit( /// depending on the message. async fn handle_room_message( event: SyncRoomMessageEvent, + timestamp: Option, cache: &RoomEventCache, ) -> Option { if let Some(event) = event.as_original() { - return handle_possible_edit(event, cache).await.or(get_most_recent_edit( + return handle_possible_edit(event, timestamp, cache).await.or(get_most_recent_edit( cache, &event.event_id, ) .await - .and_then(|recent| indexable_from_room_message(&recent).map(RoomIndexOperation::Add))); + .and_then(|recent| { + indexable_from_room_message(&recent, timestamp).map(RoomIndexOperation::Add) + })); } None } @@ -345,6 +352,7 @@ async fn handle_room_message( /// re-adding the most recent remaining version if an edit was redacted. async fn handle_room_redaction( event: SyncRoomRedactionEvent, + timestamp: Option, cache: &RoomEventCache, rules: &RedactionRules, ) -> Option { @@ -357,7 +365,7 @@ async fn handle_room_redaction( redacted_event, ))) = redacted_event.raw().deserialize() && let Some(redacted_event) = redacted_event.as_original() - && let Some(operation) = handle_possible_edit(redacted_event, cache).await + && let Some(operation) = handle_possible_edit(redacted_event, timestamp, cache).await { return Some(operation); } @@ -368,14 +376,17 @@ async fn handle_room_redaction( } /// Return a [`RoomIndexOperation::Add`] indexing a sticker's descriptive text. -fn handle_sticker(event: SyncStickerEvent) -> Option { +fn handle_sticker( + event: SyncStickerEvent, + timestamp: Option, +) -> Option { let event = event.as_original()?; Some(RoomIndexOperation::Add(IndexableEvent::new( event.event_id.clone(), event.event_id.clone(), event.sender.clone(), - event.origin_server_ts, + timestamp, event.content.body.clone(), ))) } @@ -386,7 +397,10 @@ fn handle_sticker(event: SyncStickerEvent) -> Option { /// ponytail: only indexes the initial `New` poll — edits (`Replacement`) and /// poll ends are ignored. Add edit handling if editing a poll needs to update /// search results. -fn handle_unstable_poll_start(event: SyncUnstablePollStartEvent) -> Option { +fn handle_unstable_poll_start( + event: SyncUnstablePollStartEvent, + timestamp: Option, +) -> Option { let event = event.as_original()?; let UnstablePollStartEventContent::New(content) = &event.content else { @@ -404,7 +418,7 @@ fn handle_unstable_poll_start(event: SyncUnstablePollStartEvent) -> Option Option Option { +fn handle_poll_start( + event: SyncPollStartEvent, + timestamp: Option, +) -> Option { let event = event.as_original()?; // Skip poll edits, matching the unstable poll handling. @@ -435,7 +452,7 @@ fn handle_poll_start(event: SyncPollStartEvent) -> Option { event.event_id.clone(), event.event_id.clone(), event.sender.clone(), - event.origin_server_ts, + timestamp, body, ))) } @@ -453,19 +470,21 @@ async fn parse_timeline_event( return None; } + let timestamp = event.timestamp(); + match event.raw().deserialize() { Ok(event) => match event { AnySyncTimelineEvent::MessageLike(event) => match event { AnySyncMessageLikeEvent::RoomMessage(event) => { - handle_room_message(event, cache).await + handle_room_message(event, timestamp, cache).await } AnySyncMessageLikeEvent::RoomRedaction(event) => { - handle_room_redaction(event, cache, redaction_rules).await + handle_room_redaction(event, timestamp, cache, redaction_rules).await } - AnySyncMessageLikeEvent::Sticker(event) => handle_sticker(event), - AnySyncMessageLikeEvent::PollStart(event) => handle_poll_start(event), + AnySyncMessageLikeEvent::Sticker(event) => handle_sticker(event, timestamp), + AnySyncMessageLikeEvent::PollStart(event) => handle_poll_start(event, timestamp), AnySyncMessageLikeEvent::UnstablePollStart(event) => { - handle_unstable_poll_start(event) + handle_unstable_poll_start(event, timestamp) } _ => None, },