matrix_sdk_ffi/
room_directory_search.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// Copyright 2024 Mauro Romito
// Copyright 2024 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 the specific language governing permissions and
// limitations under the License.

use std::{fmt::Debug, sync::Arc};

use eyeball_im::VectorDiff;
use futures_util::StreamExt;
use matrix_sdk::room_directory_search::RoomDirectorySearch as SdkRoomDirectorySearch;
use ruma::ServerName;
use tokio::sync::RwLock;

use super::RUNTIME;
use crate::{error::ClientError, task_handle::TaskHandle};

#[derive(uniffi::Enum)]
pub enum PublicRoomJoinRule {
    Public,
    Knock,
}

impl TryFrom<ruma::directory::PublicRoomJoinRule> for PublicRoomJoinRule {
    type Error = String;

    fn try_from(value: ruma::directory::PublicRoomJoinRule) -> Result<Self, Self::Error> {
        match value {
            ruma::directory::PublicRoomJoinRule::Public => Ok(Self::Public),
            ruma::directory::PublicRoomJoinRule::Knock => Ok(Self::Knock),
            rule => Err(format!("unsupported join rule: {rule:?}")),
        }
    }
}

#[derive(uniffi::Record)]
pub struct RoomDescription {
    pub room_id: String,
    pub name: Option<String>,
    pub topic: Option<String>,
    pub alias: Option<String>,
    pub avatar_url: Option<String>,
    pub join_rule: Option<PublicRoomJoinRule>,
    pub is_world_readable: bool,
    pub joined_members: u64,
}

impl From<matrix_sdk::room_directory_search::RoomDescription> for RoomDescription {
    fn from(value: matrix_sdk::room_directory_search::RoomDescription) -> Self {
        Self {
            room_id: value.room_id.to_string(),
            name: value.name,
            topic: value.topic,
            alias: value.alias.map(|alias| alias.to_string()),
            avatar_url: value.avatar_url.map(|url| url.to_string()),
            join_rule: value.join_rule.try_into().ok(),
            is_world_readable: value.is_world_readable,
            joined_members: value.joined_members,
        }
    }
}

/// A helper for performing room searches in the room directory.
/// The way this is intended to be used is:
///
/// 1. Register a callback using [`RoomDirectorySearch::results`].
/// 2. Start the room search with [`RoomDirectorySearch::search`].
/// 3. To get more results, use [`RoomDirectorySearch::next_page`].
#[derive(uniffi::Object)]
pub struct RoomDirectorySearch {
    pub(crate) inner: RwLock<SdkRoomDirectorySearch>,
}

impl RoomDirectorySearch {
    pub fn new(inner: SdkRoomDirectorySearch) -> Self {
        Self { inner: RwLock::new(inner) }
    }
}

#[matrix_sdk_ffi_macros::export]
impl RoomDirectorySearch {
    /// Asks the server for the next page of the current search.
    pub async fn next_page(&self) -> Result<(), ClientError> {
        let mut inner = self.inner.write().await;
        inner.next_page().await?;
        Ok(())
    }

    /// Starts a filtered search for the server.
    ///
    /// If the `filter` is not provided it will search for all the rooms.
    /// You can specify a `batch_size` to control the number of rooms to fetch
    /// per request.
    ///
    /// If the `via_server` is not provided it will search in the current
    /// homeserver by default.
    ///
    /// This method will clear the current search results and start a new one.
    pub async fn search(
        &self,
        filter: Option<String>,
        batch_size: u32,
        via_server_name: Option<String>,
    ) -> Result<(), ClientError> {
        let server = via_server_name.map(ServerName::parse).transpose()?;
        let mut inner = self.inner.write().await;
        inner.search(filter, batch_size, server).await?;
        Ok(())
    }

    /// Get the number of pages that have been loaded so far.
    pub async fn loaded_pages(&self) -> Result<u32, ClientError> {
        let inner = self.inner.read().await;
        Ok(inner.loaded_pages() as u32)
    }

    /// Get whether the search is at the last page.
    pub async fn is_at_last_page(&self) -> Result<bool, ClientError> {
        let inner = self.inner.read().await;
        Ok(inner.is_at_last_page())
    }

    /// Registers a callback to receive new search results when starting a
    /// search or getting new paginated results.
    pub async fn results(
        &self,
        listener: Box<dyn RoomDirectorySearchEntriesListener>,
    ) -> Arc<TaskHandle> {
        let (initial_values, mut stream) = self.inner.read().await.results();

        Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
            listener.on_update(vec![RoomDirectorySearchEntryUpdate::Reset {
                values: initial_values.into_iter().map(Into::into).collect(),
            }]);

            while let Some(diffs) = stream.next().await {
                listener.on_update(diffs.into_iter().map(|diff| diff.into()).collect());
            }
        })))
    }
}

#[derive(uniffi::Record)]
pub struct RoomDirectorySearchEntriesResult {
    pub entries_stream: Arc<TaskHandle>,
}

#[derive(uniffi::Enum)]
pub enum RoomDirectorySearchEntryUpdate {
    Append { values: Vec<RoomDescription> },
    Clear,
    PushFront { value: RoomDescription },
    PushBack { value: RoomDescription },
    PopFront,
    PopBack,
    Insert { index: u32, value: RoomDescription },
    Set { index: u32, value: RoomDescription },
    Remove { index: u32 },
    Truncate { length: u32 },
    Reset { values: Vec<RoomDescription> },
}

impl From<VectorDiff<matrix_sdk::room_directory_search::RoomDescription>>
    for RoomDirectorySearchEntryUpdate
{
    fn from(diff: VectorDiff<matrix_sdk::room_directory_search::RoomDescription>) -> Self {
        match diff {
            VectorDiff::Append { values } => {
                Self::Append { values: values.into_iter().map(|v| v.into()).collect() }
            }
            VectorDiff::Clear => Self::Clear,
            VectorDiff::PushFront { value } => Self::PushFront { value: value.into() },
            VectorDiff::PushBack { value } => Self::PushBack { value: value.into() },
            VectorDiff::PopFront => Self::PopFront,
            VectorDiff::PopBack => Self::PopBack,
            VectorDiff::Insert { index, value } => {
                Self::Insert { index: index as u32, value: value.into() }
            }
            VectorDiff::Set { index, value } => {
                Self::Set { index: index as u32, value: value.into() }
            }
            VectorDiff::Remove { index } => Self::Remove { index: index as u32 },
            VectorDiff::Truncate { length } => Self::Truncate { length: length as u32 },
            VectorDiff::Reset { values } => {
                Self::Reset { values: values.into_iter().map(|v| v.into()).collect() }
            }
        }
    }
}

#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait RoomDirectorySearchEntriesListener: Send + Sync + Debug {
    fn on_update(&self, room_entries_update: Vec<RoomDirectorySearchEntryUpdate>);
}