mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-04-21 15:47:54 -04:00
ffi bindings for room search
This commit is contained in:
@@ -24,7 +24,10 @@ use matrix_sdk::{
|
||||
HttpError, IdParseError, NotificationSettingsError as SdkNotificationSettingsError,
|
||||
QueueWedgeError as SdkQueueWedgeError, StoreError,
|
||||
};
|
||||
use matrix_sdk_ui::{encryption_sync_service, notification_client, spaces, sync_service, timeline};
|
||||
use matrix_sdk_ui::{
|
||||
encryption_sync_service, notification_client, search::SearchError, spaces, sync_service,
|
||||
timeline,
|
||||
};
|
||||
use ruma::{
|
||||
api::client::error::{ErrorBody, ErrorKind as RumaApiErrorKind, RetryAfter, StandardErrorBody},
|
||||
MilliSecondsSinceUnixEpoch,
|
||||
@@ -239,6 +242,12 @@ impl From<spaces::Error> for ClientError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SearchError> for ClientError {
|
||||
fn from(e: SearchError) -> Self {
|
||||
Self::from_err(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Bindings version of the sdk type replacing OwnedUserId/DeviceIds with simple
|
||||
/// String.
|
||||
///
|
||||
|
||||
@@ -24,6 +24,7 @@ mod room_member;
|
||||
mod room_preview;
|
||||
mod ruma;
|
||||
mod runtime;
|
||||
mod search;
|
||||
mod session_verification;
|
||||
mod spaces;
|
||||
mod store;
|
||||
|
||||
108
bindings/matrix-sdk-ffi/src/search.rs
Normal file
108
bindings/matrix-sdk-ffi/src/search.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2026 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for that specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use matrix_sdk::deserialized_responses::TimelineEvent;
|
||||
use matrix_sdk_ui::search::RoomSearch;
|
||||
use ruma::OwnedUserId;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
error::ClientError,
|
||||
room::Room,
|
||||
timeline::{ProfileDetails, TimelineItemContent},
|
||||
utils::Timestamp,
|
||||
};
|
||||
|
||||
#[matrix_sdk_ffi_macros::export]
|
||||
impl Room {
|
||||
pub fn search(&self, query: String) -> RoomSearchIterator {
|
||||
RoomSearchIterator {
|
||||
sdk_room: self.inner.clone(),
|
||||
inner: Mutex::new(RoomSearch::new(self.inner.clone(), query)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct RoomSearchIterator {
|
||||
sdk_room: matrix_sdk::room::Room,
|
||||
inner: Mutex<RoomSearch>,
|
||||
}
|
||||
|
||||
#[matrix_sdk_ffi_macros::export]
|
||||
impl RoomSearchIterator {
|
||||
/// Return a list of event ids for the next batch of search results, or
|
||||
/// `None` if there are no more results.
|
||||
pub async fn next(&self) -> Option<Vec<String>> {
|
||||
match self.inner.lock().await.next().await {
|
||||
Ok(Some(event_ids)) => Some(event_ids.into_iter().map(|id| id.to_string()).collect()),
|
||||
Ok(None) => None,
|
||||
Err(e) => {
|
||||
eprintln!("Error during search: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Option<Vec<RoomSearchResult>>, ClientError> {
|
||||
let Some(events) = self.inner.lock().await.next_events().await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut results = Vec::with_capacity(events.len());
|
||||
|
||||
for event in events {
|
||||
if let Some(result) = RoomSearchResult::from(&self.sdk_room, event).await {
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
results.shrink_to_fit();
|
||||
|
||||
Ok(Some(results))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct RoomSearchResult {
|
||||
event_id: String,
|
||||
sender: String,
|
||||
sender_profile: ProfileDetails,
|
||||
content: TimelineItemContent,
|
||||
timestamp: Timestamp,
|
||||
}
|
||||
|
||||
impl RoomSearchResult {
|
||||
async fn from(room: &matrix_sdk::room::Room, event: TimelineEvent) -> Option<Self> {
|
||||
// TODO: i did make an helper for this, on some branch on my machine
|
||||
let sender = event.raw().get_field::<OwnedUserId>("sender").ok().flatten()?;
|
||||
|
||||
let event_id = event.event_id().unwrap().to_string();
|
||||
let timestamp =
|
||||
event.timestamp().unwrap_or_else(ruma::MilliSecondsSinceUnixEpoch::now).into();
|
||||
|
||||
let (content, profile) =
|
||||
matrix_sdk_ui::timeline::TimelineItemContent::from_raw_event(room, event).await?;
|
||||
|
||||
Some(Self {
|
||||
event_id,
|
||||
sender: sender.to_string(),
|
||||
sender_profile: ProfileDetails::from(profile),
|
||||
content: TimelineItemContent::from(content),
|
||||
timestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -15,16 +15,18 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use as_variant::as_variant;
|
||||
use matrix_sdk::{Room, deserialized_responses::TimelineEvent};
|
||||
use matrix_sdk_base::crypto::types::events::UtdCause;
|
||||
use ruma::{
|
||||
OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedUserId, UserId,
|
||||
events::{
|
||||
AnyStateEventContentChange, Mentions, MessageLikeEventType, StateEventContentChange,
|
||||
StateEventType,
|
||||
AnyMessageLikeEventContent, AnyStateEventContentChange, Mentions, MessageLikeEventType,
|
||||
StateEventContentChange, StateEventType,
|
||||
policy::rule::{
|
||||
room::PolicyRuleRoomEventContent, server::PolicyRuleServerEventContent,
|
||||
user::PolicyRuleUserEventContent,
|
||||
},
|
||||
relation::Replacement,
|
||||
room::{
|
||||
aliases::RoomAliasesEventContent,
|
||||
avatar::RoomAvatarEventContent,
|
||||
@@ -36,7 +38,7 @@ use ruma::{
|
||||
history_visibility::RoomHistoryVisibilityEventContent,
|
||||
join_rules::RoomJoinRulesEventContent,
|
||||
member::{Change, RoomMemberEventContent},
|
||||
message::MessageType,
|
||||
message::{MessageType, RoomMessageEventContent},
|
||||
name::RoomNameEventContent,
|
||||
pinned_events::RoomPinnedEventsEventContent,
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
@@ -74,6 +76,11 @@ pub use self::{
|
||||
reply::{EmbeddedEvent, InReplyToDetails},
|
||||
};
|
||||
use super::ReactionsByKeyBySender;
|
||||
use crate::timeline::{
|
||||
Profile, TimelineDetails,
|
||||
event_handler::{HandleAggregationKind, TimelineAction},
|
||||
traits::RoomDataProvider as _,
|
||||
};
|
||||
|
||||
/// The content of an [`EventTimelineItem`][super::EventTimelineItem].
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
@@ -119,6 +126,65 @@ pub enum TimelineItemContent {
|
||||
}
|
||||
|
||||
impl TimelineItemContent {
|
||||
// TODO: commonize with the latest event implementation, likely?
|
||||
pub async fn from_raw_event(
|
||||
room: &Room,
|
||||
timeline_event: TimelineEvent,
|
||||
) -> Option<(Self, TimelineDetails<Profile>)> {
|
||||
let raw_any_sync_timeline_event = timeline_event.into_raw();
|
||||
let any_sync_timeline_event = raw_any_sync_timeline_event.deserialize().ok()?;
|
||||
|
||||
let sender = any_sync_timeline_event.sender().to_owned();
|
||||
|
||||
let profile = room
|
||||
.profile_from_user_id(&sender)
|
||||
.await
|
||||
.map(TimelineDetails::Ready)
|
||||
.unwrap_or(TimelineDetails::Unavailable);
|
||||
|
||||
match TimelineAction::from_event(
|
||||
any_sync_timeline_event,
|
||||
&raw_any_sync_timeline_event,
|
||||
room,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
// Easy path: no aggregation, direct event.
|
||||
Some(TimelineAction::AddItem { content }) => Some((content, profile)),
|
||||
|
||||
// Aggregated event.
|
||||
//
|
||||
// Only edits are supported for the moment.
|
||||
Some(TimelineAction::HandleAggregation {
|
||||
kind: HandleAggregationKind::Edit { replacement: Replacement { new_content, .. } },
|
||||
..
|
||||
}) => {
|
||||
// Let's map the edit into a regular message.
|
||||
match TimelineAction::from_content(
|
||||
AnyMessageLikeEventContent::RoomMessage(RoomMessageEventContent::new(
|
||||
new_content.msgtype,
|
||||
)),
|
||||
// We don't care about the `InReplyToDetails` in the context of a
|
||||
// `LatestEventValue`.
|
||||
None,
|
||||
// We don't care about the thread information in the context of a
|
||||
// `LatestEventValue`.
|
||||
None,
|
||||
None,
|
||||
) {
|
||||
TimelineAction::AddItem { content } => Some((content, profile)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_msglike(&self) -> Option<&MsgLikeContent> {
|
||||
as_variant!(self, TimelineItemContent::MsgLike)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user