diff --git a/bindings/matrix-sdk-ffi/src/client_builder.rs b/bindings/matrix-sdk-ffi/src/client_builder.rs index f790bc5df..2c591f9b1 100644 --- a/bindings/matrix-sdk-ffi/src/client_builder.rs +++ b/bindings/matrix-sdk-ffi/src/client_builder.rs @@ -360,6 +360,17 @@ impl ClientBuilder { Arc::new(builder) } + /// Set up the search index store for this client, which is used to store + /// the message search index locally. + /// + /// As soon as this is enabled, messages will start to be indexed, and can + /// be later queried for search. + /// + /// `path` is the directory where the search index will be stored. It must + /// be unique per session. + /// + /// `password` is an optional password to encrypt the search index at rest. + /// If `None`, the search index will be stored unencrypted. pub fn with_search_index_store( self: Arc, path: String, diff --git a/bindings/matrix-sdk-ffi/src/error.rs b/bindings/matrix-sdk-ffi/src/error.rs index c34c9c502..a9159a001 100644 --- a/bindings/matrix-sdk-ffi/src/error.rs +++ b/bindings/matrix-sdk-ffi/src/error.rs @@ -24,10 +24,7 @@ use matrix_sdk::{ room::{calls::CallError, edit::EditError}, send_queue::RoomSendQueueError, }; -use matrix_sdk_ui::{ - encryption_sync_service, notification_client, search::SearchError, spaces, sync_service, - timeline, -}; +use matrix_sdk_ui::{encryption_sync_service, notification_client, spaces, sync_service, timeline}; use ruma::{ MilliSecondsSinceUnixEpoch, api::error::{ErrorBody, ErrorKind as RumaApiErrorKind, RetryAfter, StandardErrorBody}, @@ -241,12 +238,6 @@ impl From for ClientError { } } -impl From for ClientError { - fn from(e: SearchError) -> Self { - Self::from_err(e) - } -} - /// Bindings version of the sdk type replacing OwnedUserId/DeviceIds with simple /// String. /// diff --git a/bindings/matrix-sdk-ffi/src/search.rs b/bindings/matrix-sdk-ffi/src/search.rs index 59570b501..a8b444d79 100644 --- a/bindings/matrix-sdk-ffi/src/search.rs +++ b/bindings/matrix-sdk-ffi/src/search.rs @@ -16,7 +16,7 @@ use matrix_sdk::deserialized_responses::TimelineEvent; use matrix_sdk_ui::{ search::{ GlobalSearchIterator as SdkGlobalSearchIterator, - RoomSearchIterator as SdkRoomSearchIterator, + RoomSearchIterator as SdkRoomSearchIterator, SearchError as SdkSearchError, }, timeline::TimelineDetails, }; @@ -30,6 +30,23 @@ use crate::{ utils::Timestamp, }; +#[derive(uniffi::Error, thiserror::Error, Debug)] +pub enum SearchError { + #[error("Failed to search through the index: {0}")] + IndexError(String), + #[error("Failed to load event content for search result: {0}")] + EventLoadError(String), +} + +impl From for SearchError { + fn from(err: SdkSearchError) -> Self { + match err { + SdkSearchError::IndexError(err) => SearchError::IndexError(err.to_string()), + SdkSearchError::EventLoadError(err) => SearchError::EventLoadError(err.to_string()), + } + } +} + #[matrix_sdk_ffi_macros::export] impl Room { pub fn search(&self, query: String) -> RoomSearchIterator { @@ -50,7 +67,7 @@ pub struct RoomSearchIterator { impl RoomSearchIterator { /// Return a list of events for the next batch of search results, or `None` /// if there are no more results. - pub async fn next_events(&self) -> Result>, ClientError> { + pub async fn next_events(&self) -> Result>, SearchError> { let Some(events) = self.inner.lock().await.next_events(20).await? else { return Ok(None); }; @@ -103,16 +120,18 @@ impl RoomSearchResult { #[derive(Clone, uniffi::Enum)] pub enum SearchRoomFilter { - /// All the joined rooms (= DMs + groups). + /// All the joined rooms (= DMs + non-DMs). Rooms, /// Only joined DM rooms. Dms, /// Only joined non-DM (group) rooms. - Groups, + NonDms, } #[matrix_sdk_ffi_macros::export] impl Client { + /// Search across all all rooms for the given query, returning an iterator + /// over the results. pub async fn search( &self, query: String, @@ -124,7 +143,7 @@ impl Client { match filter { SearchRoomFilter::Rooms => {} SearchRoomFilter::Dms => search = search.only_dm_rooms().await?, - SearchRoomFilter::Groups => search = search.only_groups().await?, + SearchRoomFilter::NonDms => search = search.no_dms().await?, } Ok(GlobalSearchIterator { sdk_client, inner: Mutex::new(search.build()) }) @@ -156,7 +175,7 @@ impl GlobalSearchIterator { pub async fn next_events( &self, num_results: u64, - ) -> Result>, ClientError> { + ) -> Result>, SearchError> { let Some(events) = self.inner.lock().await.next_events(num_results as usize).await? else { return Ok(None); }; diff --git a/crates/matrix-sdk-ui/Cargo.toml b/crates/matrix-sdk-ui/Cargo.toml index 0b1df5117..e89881ab8 100644 --- a/crates/matrix-sdk-ui/Cargo.toml +++ b/crates/matrix-sdk-ui/Cargo.toml @@ -28,7 +28,7 @@ experimental-encrypted-state-events = [ "ruma/unstable-msc4362" ] -experimental-search = ["matrix-sdk/experimental-search", "matrix-sdk-search"] +experimental-search = ["matrix-sdk/experimental-search", "dep:matrix-sdk-search"] [dependencies] as_variant.workspace = true diff --git a/crates/matrix-sdk-ui/src/search/mod.rs b/crates/matrix-sdk-ui/src/search/mod.rs index 613486a2d..b631e55d7 100644 --- a/crates/matrix-sdk-ui/src/search/mod.rs +++ b/crates/matrix-sdk-ui/src/search/mod.rs @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! High-level search helpers that provide more convenient APIs on top of the +//! low-level search API provided by the `search` module, such as streams for +//! paginating search results and builders for configuring search queries. + use std::collections::HashMap; use imbl::HashSet; @@ -20,6 +24,8 @@ use matrix_sdk_base::RoomStateFilter; use matrix_sdk_search::error::IndexError; use ruma::{OwnedEventId, OwnedRoomId}; +/// An error that can occur while searching messages, using the high-level +/// search helpers provided by this module provided by this module. #[derive(thiserror::Error, Debug)] pub enum SearchError { #[error(transparent)] @@ -57,6 +63,8 @@ impl RoomSearchIterator { return Ok(None); } + // TODO: use the client/server API search endpoint for public rooms, as those + // may require lots of time for indexing all events. let result = self.room.search(&self.query, num_results, self.offset).await?; if result.is_empty() { @@ -101,8 +109,8 @@ impl GlobalSearchRoomState { } } -/// A builder for a [`GlobalSearchIterator`] that allows to configure the initial -/// working set of rooms to search in. +/// A builder for a [`GlobalSearchIterator`] that allows to configure the +/// initial working set of rooms to search in. pub struct GlobalSearchBuilder { /// The search query, directly forwarded to the search API. query: String, @@ -135,7 +143,7 @@ impl GlobalSearchBuilder { } /// Keep only non-DM rooms (groups) from the initial working set. - pub async fn only_groups(mut self) -> Result { + pub async fn no_dms(mut self) -> Result { let mut to_remove = HashSet::new(); for room in &self.room_set { if room.is_direct().await? { @@ -496,7 +504,7 @@ mod tests { // Search for an existing keyword, by event, only in groups. { let mut search = GlobalSearchIterator::builder(client.clone(), "world".to_owned()) - .only_groups() + .no_dms() .await .unwrap() .build(); diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index cfe11e3c0..2f855b5b8 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -737,6 +737,11 @@ pub enum TimelineDetails { } impl TimelineDetails { + /// Create a [`TimelineDetails`] from an initial value that may or may not + /// be available. + /// + /// Will be [`TimelineDetails::Ready`] if the value is `Some(_)`, and + /// [`TimelineDetails::Unavailable`] if the value is `None`. pub fn from_initial_value(value: Option) -> Self { match value { Some(v) => Self::Ready(v),