matrix_sdk_ffi/
room_directory_search.rs

1// Copyright 2024 Mauro Romito
2// Copyright 2024 The Matrix.org Foundation C.I.C.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use std::{fmt::Debug, sync::Arc};
17
18use eyeball_im::VectorDiff;
19use futures_util::StreamExt;
20use matrix_sdk::room_directory_search::RoomDirectorySearch as SdkRoomDirectorySearch;
21use ruma::ServerName;
22use tokio::sync::RwLock;
23
24use super::RUNTIME;
25use crate::{error::ClientError, task_handle::TaskHandle};
26
27#[derive(uniffi::Enum)]
28pub enum PublicRoomJoinRule {
29    Public,
30    Knock,
31}
32
33impl TryFrom<ruma::directory::PublicRoomJoinRule> for PublicRoomJoinRule {
34    type Error = String;
35
36    fn try_from(value: ruma::directory::PublicRoomJoinRule) -> Result<Self, Self::Error> {
37        match value {
38            ruma::directory::PublicRoomJoinRule::Public => Ok(Self::Public),
39            ruma::directory::PublicRoomJoinRule::Knock => Ok(Self::Knock),
40            rule => Err(format!("unsupported join rule: {rule:?}")),
41        }
42    }
43}
44
45#[derive(uniffi::Record)]
46pub struct RoomDescription {
47    pub room_id: String,
48    pub name: Option<String>,
49    pub topic: Option<String>,
50    pub alias: Option<String>,
51    pub avatar_url: Option<String>,
52    pub join_rule: Option<PublicRoomJoinRule>,
53    pub is_world_readable: bool,
54    pub joined_members: u64,
55}
56
57impl From<matrix_sdk::room_directory_search::RoomDescription> for RoomDescription {
58    fn from(value: matrix_sdk::room_directory_search::RoomDescription) -> Self {
59        Self {
60            room_id: value.room_id.to_string(),
61            name: value.name,
62            topic: value.topic,
63            alias: value.alias.map(|alias| alias.to_string()),
64            avatar_url: value.avatar_url.map(|url| url.to_string()),
65            join_rule: value.join_rule.try_into().ok(),
66            is_world_readable: value.is_world_readable,
67            joined_members: value.joined_members,
68        }
69    }
70}
71
72/// A helper for performing room searches in the room directory.
73/// The way this is intended to be used is:
74///
75/// 1. Register a callback using [`RoomDirectorySearch::results`].
76/// 2. Start the room search with [`RoomDirectorySearch::search`].
77/// 3. To get more results, use [`RoomDirectorySearch::next_page`].
78#[derive(uniffi::Object)]
79pub struct RoomDirectorySearch {
80    pub(crate) inner: RwLock<SdkRoomDirectorySearch>,
81}
82
83impl RoomDirectorySearch {
84    pub fn new(inner: SdkRoomDirectorySearch) -> Self {
85        Self { inner: RwLock::new(inner) }
86    }
87}
88
89#[matrix_sdk_ffi_macros::export]
90impl RoomDirectorySearch {
91    /// Asks the server for the next page of the current search.
92    pub async fn next_page(&self) -> Result<(), ClientError> {
93        let mut inner = self.inner.write().await;
94        inner.next_page().await?;
95        Ok(())
96    }
97
98    /// Starts a filtered search for the server.
99    ///
100    /// If the `filter` is not provided it will search for all the rooms.
101    /// You can specify a `batch_size` to control the number of rooms to fetch
102    /// per request.
103    ///
104    /// If the `via_server` is not provided it will search in the current
105    /// homeserver by default.
106    ///
107    /// This method will clear the current search results and start a new one.
108    pub async fn search(
109        &self,
110        filter: Option<String>,
111        batch_size: u32,
112        via_server_name: Option<String>,
113    ) -> Result<(), ClientError> {
114        let server = via_server_name.map(ServerName::parse).transpose()?;
115        let mut inner = self.inner.write().await;
116        inner.search(filter, batch_size, server).await?;
117        Ok(())
118    }
119
120    /// Get the number of pages that have been loaded so far.
121    pub async fn loaded_pages(&self) -> Result<u32, ClientError> {
122        let inner = self.inner.read().await;
123        Ok(inner.loaded_pages() as u32)
124    }
125
126    /// Get whether the search is at the last page.
127    pub async fn is_at_last_page(&self) -> Result<bool, ClientError> {
128        let inner = self.inner.read().await;
129        Ok(inner.is_at_last_page())
130    }
131
132    /// Registers a callback to receive new search results when starting a
133    /// search or getting new paginated results.
134    pub async fn results(
135        &self,
136        listener: Box<dyn RoomDirectorySearchEntriesListener>,
137    ) -> Arc<TaskHandle> {
138        let (initial_values, mut stream) = self.inner.read().await.results();
139
140        Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
141            listener.on_update(vec![RoomDirectorySearchEntryUpdate::Reset {
142                values: initial_values.into_iter().map(Into::into).collect(),
143            }]);
144
145            while let Some(diffs) = stream.next().await {
146                listener.on_update(diffs.into_iter().map(|diff| diff.into()).collect());
147            }
148        })))
149    }
150}
151
152#[derive(uniffi::Record)]
153pub struct RoomDirectorySearchEntriesResult {
154    pub entries_stream: Arc<TaskHandle>,
155}
156
157#[derive(uniffi::Enum)]
158pub enum RoomDirectorySearchEntryUpdate {
159    Append { values: Vec<RoomDescription> },
160    Clear,
161    PushFront { value: RoomDescription },
162    PushBack { value: RoomDescription },
163    PopFront,
164    PopBack,
165    Insert { index: u32, value: RoomDescription },
166    Set { index: u32, value: RoomDescription },
167    Remove { index: u32 },
168    Truncate { length: u32 },
169    Reset { values: Vec<RoomDescription> },
170}
171
172impl From<VectorDiff<matrix_sdk::room_directory_search::RoomDescription>>
173    for RoomDirectorySearchEntryUpdate
174{
175    fn from(diff: VectorDiff<matrix_sdk::room_directory_search::RoomDescription>) -> Self {
176        match diff {
177            VectorDiff::Append { values } => {
178                Self::Append { values: values.into_iter().map(|v| v.into()).collect() }
179            }
180            VectorDiff::Clear => Self::Clear,
181            VectorDiff::PushFront { value } => Self::PushFront { value: value.into() },
182            VectorDiff::PushBack { value } => Self::PushBack { value: value.into() },
183            VectorDiff::PopFront => Self::PopFront,
184            VectorDiff::PopBack => Self::PopBack,
185            VectorDiff::Insert { index, value } => {
186                Self::Insert { index: index as u32, value: value.into() }
187            }
188            VectorDiff::Set { index, value } => {
189                Self::Set { index: index as u32, value: value.into() }
190            }
191            VectorDiff::Remove { index } => Self::Remove { index: index as u32 },
192            VectorDiff::Truncate { length } => Self::Truncate { length: length as u32 },
193            VectorDiff::Reset { values } => {
194                Self::Reset { values: values.into_iter().map(|v| v.into()).collect() }
195            }
196        }
197    }
198}
199
200#[matrix_sdk_ffi_macros::export(callback_interface)]
201pub trait RoomDirectorySearchEntriesListener: Send + Sync + Debug {
202    fn on_update(&self, room_entries_update: Vec<RoomDirectorySearchEntryUpdate>);
203}