atagen

matrix-rust-sdk

public
Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk.git
bindings/matrix-sdk-ffi/src/search.rsRust193 lines6.0 KB
1
// Copyright 2026 The Matrix.org Foundation C.I.C.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for that specific language governing permissions and
13
// limitations under the License.
14
15
use matrix_sdk::{
16
    deserialized_responses::TimelineEvent,
17
    message_search::{
18
        GlobalSearchIterator as SdkGlobalSearchIterator,
19
        RoomSearchIterator as SdkRoomSearchIterator, SearchError as SdkSearchError,
20
    },
21
};
22
use matrix_sdk_ui::timeline::TimelineDetails;
23
use tokio::sync::Mutex;
24
25
use crate::{
26
    client::Client,
27
    error::ClientError,
28
    room::Room,
29
    timeline::{ProfileDetails, TimelineItemContent},
30
    utils::Timestamp,
31
};
32
33
#[derive(uniffi::Error, thiserror::Error, Debug)]
34
pub enum SearchError {
35
    #[error("Failed to search through the index: {0}")]
36
    IndexError(String),
37
    #[error("Failed to load event content for search result: {0}")]
38
    EventLoadError(String),
39
}
40
41
impl From<SdkSearchError> for SearchError {
42
    fn from(err: SdkSearchError) -> Self {
43
        match err {
44
            SdkSearchError::IndexError(err) => SearchError::IndexError(err.to_string()),
45
            SdkSearchError::EventLoadError(err) => SearchError::EventLoadError(err.to_string()),
46
        }
47
    }
48
}
49
50
#[matrix_sdk_ffi_macros::export]
51
impl Room {
52
    /// Search for messages in this room matching the given query, returning an
53
    /// iterator over the results that yields `num_results_per_batch` results at
54
    /// a time.
55
    pub fn search_messages(&self, query: String, num_results_per_batch: u32) -> RoomSearchIterator {
56
        RoomSearchIterator {
57
            sdk_room: (*self.inner).clone(),
58
            inner: Mutex::new(self.inner.search_messages(query, num_results_per_batch as usize)),
59
        }
60
    }
61
}
62
63
#[derive(uniffi::Object)]
64
pub struct RoomSearchIterator {
65
    sdk_room: matrix_sdk::room::Room,
66
    inner: Mutex<SdkRoomSearchIterator>,
67
}
68
69
#[matrix_sdk_ffi_macros::export]
70
impl RoomSearchIterator {
71
    /// Return a list of events for the next batch of search results, or `None`
72
    /// if there are no more results.
73
    pub async fn next_events(&self) -> Result<Option<Vec<RoomSearchResult>>, SearchError> {
74
        let Some(events) = self.inner.lock().await.next_events().await? else {
75
            return Ok(None);
76
        };
77
78
        let mut results = Vec::with_capacity(events.len());
79
80
        for event in events {
81
            if let Some(result) = RoomSearchResult::from(&self.sdk_room, event).await {
82
                results.push(result);
83
            }
84
        }
85
86
        results.shrink_to_fit();
87
88
        Ok(Some(results))
89
    }
90
}
91
92
#[derive(Clone, uniffi::Record)]
93
pub struct RoomSearchResult {
94
    event_id: String,
95
    sender: String,
96
    sender_profile: ProfileDetails,
97
    content: TimelineItemContent,
98
    timestamp: Timestamp,
99
}
100
101
impl RoomSearchResult {
102
    async fn from(room: &matrix_sdk::room::Room, event: TimelineEvent) -> Option<Self> {
103
        let sender = event.sender()?;
104
105
        let event_id = event.event_id().unwrap().to_string();
106
        let timestamp =
107
            event.timestamp().unwrap_or_else(ruma::MilliSecondsSinceUnixEpoch::now).into();
108
109
        let content = matrix_sdk_ui::timeline::TimelineItemContent::from_event(room, event).await?;
110
        let profile = TimelineDetails::from_initial_value(
111
            matrix_sdk_ui::timeline::Profile::load(room, &sender).await,
112
        );
113
114
        Some(Self {
115
            event_id,
116
            sender: sender.to_string(),
117
            sender_profile: ProfileDetails::from(profile),
118
            content: TimelineItemContent::from(content),
119
            timestamp,
120
        })
121
    }
122
}
123
124
#[derive(Clone, uniffi::Enum)]
125
pub enum SearchRoomFilter {
126
    /// All the joined rooms (= DMs + non-DMs).
127
    Rooms,
128
    /// Only joined DM rooms.
129
    Dms,
130
    /// Only joined non-DM (group) rooms.
131
    NonDms,
132
}
133
134
#[matrix_sdk_ffi_macros::export]
135
impl Client {
136
    /// Search across all all rooms for the given query, returning an iterator
137
    /// over the results.
138
    pub async fn search_messages(
139
        &self,
140
        query: String,
141
        filter: SearchRoomFilter,
142
        num_results_per_batch: u32,
143
    ) -> Result<GlobalSearchIterator, ClientError> {
144
        let sdk_client = (*self.inner).clone();
145
        let mut search = sdk_client.search_messages(query, num_results_per_batch as usize);
146
147
        match filter {
148
            SearchRoomFilter::Rooms => {}
149
            SearchRoomFilter::Dms => search = search.only_dm_rooms().await?,
150
            SearchRoomFilter::NonDms => search = search.no_dms().await?,
151
        }
152
153
        Ok(GlobalSearchIterator { sdk_client, inner: Mutex::new(search.build()) })
154
    }
155
}
156
157
#[derive(uniffi::Record)]
158
pub struct GlobalSearchResult {
159
    room_id: String,
160
    result: RoomSearchResult,
161
}
162
163
#[derive(uniffi::Object)]
164
pub struct GlobalSearchIterator {
165
    sdk_client: matrix_sdk::Client,
166
    inner: Mutex<SdkGlobalSearchIterator>,
167
}
168
169
#[matrix_sdk_ffi_macros::export]
170
impl GlobalSearchIterator {
171
    /// Return a list of events for the next batch of search results, or `None`
172
    /// if there are no more results.
173
    pub async fn next_events(&self) -> Result<Option<Vec<GlobalSearchResult>>, SearchError> {
174
        let Some(events) = self.inner.lock().await.next_events().await? else {
175
            return Ok(None);
176
        };
177
178
        let mut results = Vec::with_capacity(events.len());
179
180
        for (room_id, event) in events {
181
            let Some(room) = self.sdk_client.get_room(&room_id) else {
182
                continue;
183
            };
184
            if let Some(result) = RoomSearchResult::from(&room, event).await {
185
                results.push(GlobalSearchResult { room_id: room_id.to_string(), result });
186
            }
187
        }
188
189
        results.shrink_to_fit();
190
191
        Ok(Some(results))
192
    }
193
}