From 18c155beb5ea10acbb3622c14912785e141d0357 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Thu, 22 Feb 2024 17:50:07 +0100 Subject: [PATCH 01/30] feat: room_directory_sync basic sync loop --- crates/matrix-sdk/src/lib.rs | 1 + crates/matrix-sdk/src/room_directory_sync.rs | 53 ++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 crates/matrix-sdk/src/room_directory_sync.rs diff --git a/crates/matrix-sdk/src/lib.rs b/crates/matrix-sdk/src/lib.rs index 63211aa91be..77cb7f60840 100644 --- a/crates/matrix-sdk/src/lib.rs +++ b/crates/matrix-sdk/src/lib.rs @@ -48,6 +48,7 @@ pub mod notification_settings; #[cfg(feature = "experimental-oidc")] pub mod oidc; pub mod room; +pub mod room_directory_sync; pub mod utils; pub mod futures { //! Named futures returned from methods on types in [the crate root][crate]. diff --git a/crates/matrix-sdk/src/room_directory_sync.rs b/crates/matrix-sdk/src/room_directory_sync.rs new file mode 100644 index 00000000000..abecfabce72 --- /dev/null +++ b/crates/matrix-sdk/src/room_directory_sync.rs @@ -0,0 +1,53 @@ +// 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 eyeball_im::ObservableVector; +use futures_core::Stream; +use ruma::OwnedRoomId; + +use crate::Client; + +#[derive(Clone)] +struct RoomDescription { + room_id: OwnedRoomId, +} +struct RoomDirectorySync { + next_token: Option, + client: Client, + results: ObservableVector, +} + +impl RoomDirectorySync { + async fn sync(&mut self) { + // TODO: we may want to have the limit be a configurable parameter of the sync? + // TODO: same for the server? + loop { + let response = self.client.public_rooms(None, self.next_token.as_deref(), None).await; + if let Ok(response) = response { + self.next_token = response.next_batch.clone(); + self.results.append( + response + .chunk + .into_iter() + .map(|room| RoomDescription { room_id: room.room_id }) + .collect(), + ); + if self.next_token.is_none() { + break; + } + } + } + } +} From 8ac6845607665aca6afb48b283c69827c1b45d9c Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Mon, 26 Feb 2024 17:32:52 +0100 Subject: [PATCH 02/30] feat: paginated public room search --- crates/matrix-sdk/src/lib.rs | 2 +- .../matrix-sdk/src/room_directory_search.rs | 97 +++++++++++++++++++ crates/matrix-sdk/src/room_directory_sync.rs | 53 ---------- .../src/tests.rs | 1 + .../src/tests/room_directory_search.rs | 71 ++++++++++++++ 5 files changed, 170 insertions(+), 54 deletions(-) create mode 100644 crates/matrix-sdk/src/room_directory_search.rs delete mode 100644 crates/matrix-sdk/src/room_directory_sync.rs create mode 100644 testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs diff --git a/crates/matrix-sdk/src/lib.rs b/crates/matrix-sdk/src/lib.rs index 77cb7f60840..4134fa3c9a3 100644 --- a/crates/matrix-sdk/src/lib.rs +++ b/crates/matrix-sdk/src/lib.rs @@ -48,7 +48,7 @@ pub mod notification_settings; #[cfg(feature = "experimental-oidc")] pub mod oidc; pub mod room; -pub mod room_directory_sync; +pub mod room_directory_search; pub mod utils; pub mod futures { //! Named futures returned from methods on types in [the crate root][crate]. diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs new file mode 100644 index 00000000000..13a94032aed --- /dev/null +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -0,0 +1,97 @@ +// 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 eyeball_im::{ObservableVector, VectorDiff}; +use futures_core::Stream; +use ruma::{ + api::client::directory::get_public_rooms_filtered::v3::Request as PublicRoomsFilterRequest, + directory::{Filter, PublicRoomJoinRule}, + OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, +}; + +use crate::Client; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RoomDescription { + pub room_id: OwnedRoomId, + pub name: Option, + pub topic: Option, + pub alias: Option, + pub avatar_url: Option, + pub join_rule: PublicRoomJoinRule, + pub is_world_readable: bool, + pub joined_members: u64, +} + +pub struct RoomDirectorySearch { + batch_size: u32, + filter: Option, + next_token: Option, + client: Client, + results: ObservableVector, +} + +impl RoomDirectorySearch { + pub fn new(client: Client) -> Self { + Self { + batch_size: 0, + filter: None, + next_token: None, + client, + results: ObservableVector::new(), + } + } + + pub async fn search(&mut self, filter: Option, batch_size: u32) { + self.filter = filter; + self.batch_size = batch_size; + self.next_token = None; + self.results.clear(); + self.next_page().await; + } + + pub async fn next_page(&mut self) { + let mut filter = Filter::new(); + filter.generic_search_term = self.filter.clone(); + + let mut request = PublicRoomsFilterRequest::new(); + request.filter = filter; + request.limit = Some(self.batch_size.into()); + request.since = self.next_token.clone(); + if let Ok(response) = self.client.public_rooms_filtered(request).await { + self.next_token = response.next_batch; + self.results.append( + response + .chunk + .into_iter() + .map(|room| RoomDescription { + room_id: room.room_id, + name: room.name, + topic: room.topic, + alias: room.canonical_alias, + avatar_url: room.avatar_url, + join_rule: room.join_rule, + is_world_readable: room.world_readable, + joined_members: room.num_joined_members.into(), + }) + .collect(), + ); + } + } + + pub fn results(&self) -> impl Stream> { + self.results.subscribe().into_stream() + } +} diff --git a/crates/matrix-sdk/src/room_directory_sync.rs b/crates/matrix-sdk/src/room_directory_sync.rs deleted file mode 100644 index abecfabce72..00000000000 --- a/crates/matrix-sdk/src/room_directory_sync.rs +++ /dev/null @@ -1,53 +0,0 @@ -// 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 eyeball_im::ObservableVector; -use futures_core::Stream; -use ruma::OwnedRoomId; - -use crate::Client; - -#[derive(Clone)] -struct RoomDescription { - room_id: OwnedRoomId, -} -struct RoomDirectorySync { - next_token: Option, - client: Client, - results: ObservableVector, -} - -impl RoomDirectorySync { - async fn sync(&mut self) { - // TODO: we may want to have the limit be a configurable parameter of the sync? - // TODO: same for the server? - loop { - let response = self.client.public_rooms(None, self.next_token.as_deref(), None).await; - if let Ok(response) = response { - self.next_token = response.next_batch.clone(); - self.results.append( - response - .chunk - .into_iter() - .map(|room| RoomDescription { room_id: room.room_id }) - .collect(), - ); - if self.next_token.is_none() { - break; - } - } - } - } -} diff --git a/testing/matrix-sdk-integration-testing/src/tests.rs b/testing/matrix-sdk-integration-testing/src/tests.rs index ee80945ad2f..507d8e4b6ab 100644 --- a/testing/matrix-sdk-integration-testing/src/tests.rs +++ b/testing/matrix-sdk-integration-testing/src/tests.rs @@ -4,4 +4,5 @@ mod invitations; mod reactions; mod redaction; mod repeated_join; +mod room_directory_search; mod sliding_sync; diff --git a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs new file mode 100644 index 00000000000..9e3d93a42aa --- /dev/null +++ b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs @@ -0,0 +1,71 @@ +// 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 anyhow::Result; +use eyeball_im::VectorDiff; +use futures::{FutureExt, StreamExt}; +use matrix_sdk::{ + room_directory_search::RoomDirectorySearch, + ruma::api::client::room::{create_room::v3::Request as CreateRoomRequest, Visibility}, +}; +use stream_assert::{assert_next_eq, assert_pending}; + +use crate::helpers::TestClientBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn test_room_directory_search_no_filter() -> Result<()> { + let alice = TestClientBuilder::new("alice".to_owned()).use_sqlite().build().await?; + for _ in 0..10 { + let mut request: CreateRoomRequest = CreateRoomRequest::new(); + request.visibility = Visibility::Public; + alice.create_room(request).await?; + } + let mut room_directory_search = RoomDirectorySearch::new(alice); + let mut stream = room_directory_search.results(); + room_directory_search.search(None, 10).await; + assert_next_eq!(stream, VectorDiff::Clear); + if let VectorDiff::Append { values } = stream.next().now_or_never().unwrap().unwrap() { + assert_eq!(values.len(), 10); + } else { + panic!("Expected a Vector::Append"); + } + assert_pending!(stream); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_room_directory_search_filter() -> Result<()> { + let alice = TestClientBuilder::new("alice".to_owned()).use_sqlite().build().await?; + let mut request: CreateRoomRequest = CreateRoomRequest::new(); + request.visibility = Visibility::Public; + alice.create_room(request).await?; + + let mut request: CreateRoomRequest = CreateRoomRequest::new(); + request.visibility = Visibility::Public; + request.name = Some("test".to_owned()); + alice.create_room(request).await?; + + let mut room_directory_search = RoomDirectorySearch::new(alice); + let mut stream = room_directory_search.results(); + room_directory_search.search(Some("test".to_owned()), 10).await; + assert_next_eq!(stream, VectorDiff::Clear); + if let VectorDiff::Append { values } = stream.next().now_or_never().unwrap().unwrap() { + assert_eq!(values.len(), 1); + } else { + panic!("Expected a Vector::Append"); + } + assert_pending!(stream); + Ok(()) +} From a79c5286d7c7764e5b0f1ae8b939cf51722ec525 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 27 Feb 2024 10:58:13 +0100 Subject: [PATCH 03/30] improved the tests --- .../matrix-sdk/src/room_directory_search.rs | 16 ++++++++++++++ .../src/tests/room_directory_search.rs | 22 ++++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 13a94032aed..844f12df934 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -41,6 +41,7 @@ pub struct RoomDirectorySearch { next_token: Option, client: Client, results: ObservableVector, + is_at_last_page: bool, } impl RoomDirectorySearch { @@ -51,6 +52,7 @@ impl RoomDirectorySearch { next_token: None, client, results: ObservableVector::new(), + is_at_last_page: false, } } @@ -59,10 +61,14 @@ impl RoomDirectorySearch { self.batch_size = batch_size; self.next_token = None; self.results.clear(); + self.is_at_last_page = false; self.next_page().await; } pub async fn next_page(&mut self) { + if self.is_at_last_page { + return; + } let mut filter = Filter::new(); filter.generic_search_term = self.filter.clone(); @@ -72,6 +78,9 @@ impl RoomDirectorySearch { request.since = self.next_token.clone(); if let Ok(response) = self.client.public_rooms_filtered(request).await { self.next_token = response.next_batch; + if self.next_token.is_none() { + self.is_at_last_page = true; + } self.results.append( response .chunk @@ -94,4 +103,11 @@ impl RoomDirectorySearch { pub fn results(&self) -> impl Stream> { self.results.subscribe().into_stream() } + + pub fn loaded_pages(&self) -> usize { + if self.batch_size == 0 { + return 0; + } + self.results.len() / self.batch_size as usize + } } diff --git a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs index 9e3d93a42aa..0bfc61caa27 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs @@ -21,27 +21,43 @@ use matrix_sdk::{ ruma::api::client::room::{create_room::v3::Request as CreateRoomRequest, Visibility}, }; use stream_assert::{assert_next_eq, assert_pending}; +use tracing::warn; use crate::helpers::TestClientBuilder; #[tokio::test(flavor = "multi_thread")] async fn test_room_directory_search_no_filter() -> Result<()> { let alice = TestClientBuilder::new("alice".to_owned()).use_sqlite().build().await?; - for _ in 0..10 { + for index in 0..8 { let mut request: CreateRoomRequest = CreateRoomRequest::new(); request.visibility = Visibility::Public; + let name = format!("test_room_{}", index); + request.name = Some(name); alice.create_room(request).await?; } let mut room_directory_search = RoomDirectorySearch::new(alice); let mut stream = room_directory_search.results(); - room_directory_search.search(None, 10).await; + room_directory_search.search(None, 5).await; assert_next_eq!(stream, VectorDiff::Clear); if let VectorDiff::Append { values } = stream.next().now_or_never().unwrap().unwrap() { - assert_eq!(values.len(), 10); + warn!("Values: {:?}", values); + assert_eq!(values.len(), 5); } else { panic!("Expected a Vector::Append"); } assert_pending!(stream); + + room_directory_search.next_page().await; + if let VectorDiff::Append { values } = stream.next().now_or_never().unwrap().unwrap() { + warn!("Values: {:?}", values); + assert_eq!(values.len(), 3); + } else { + panic!("Expected a Vector::Append"); + } + assert_pending!(stream); + + room_directory_search.next_page().await; + assert_pending!(stream); Ok(()) } From 26b0b32e5505901a7167dbf03377cfaed60a8b0b Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 27 Feb 2024 14:37:22 +0100 Subject: [PATCH 04/30] feat(bindings): ffi layer started implementation also improved the integration test for the filtered case --- bindings/matrix-sdk-ffi/src/client.rs | 9 +++ bindings/matrix-sdk-ffi/src/lib.rs | 1 + .../src/room_directory_search.rs | 73 +++++++++++++++++++ .../matrix-sdk/src/room_directory_search.rs | 56 +++++++------- .../src/tests/room_directory_search.rs | 62 ++++++---------- 5 files changed, 137 insertions(+), 64 deletions(-) create mode 100644 bindings/matrix-sdk-ffi/src/room_directory_search.rs diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 54406134ef0..cde3c488a2c 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -58,6 +58,7 @@ use crate::{ encryption::Encryption, notification::NotificationClientBuilder, notification_settings::NotificationSettings, + room_directory_search::RoomDirectorySearch, sync_service::{SyncService, SyncServiceBuilder}, task_handle::TaskHandle, ClientError, @@ -740,6 +741,14 @@ impl Client { } }))) } + + pub fn room_directory_search(&self) -> Arc { + Arc::new(RoomDirectorySearch { + inner: matrix_sdk::room_directory_search::RoomDirectorySearch::new( + (*self.inner).clone(), + ), + }) + } } #[uniffi::export(callback_interface)] diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index bcc60db3a56..d90deea3e10 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -32,6 +32,7 @@ mod notification; mod notification_settings; mod platform; mod room; +mod room_directory_search; mod room_info; mod room_list; mod room_member; diff --git a/bindings/matrix-sdk-ffi/src/room_directory_search.rs b/bindings/matrix-sdk-ffi/src/room_directory_search.rs new file mode 100644 index 00000000000..11dff161984 --- /dev/null +++ b/bindings/matrix-sdk-ffi/src/room_directory_search.rs @@ -0,0 +1,73 @@ +// 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 matrix_sdk::{room_directory_search::RoomDirectorySearch as SdkRoomDirectorySearch, Client}; + +use crate::error::ClientError; + +#[derive(uniffi::Enum)] +pub enum PublicRoomJoinRule { + Public, + Knock, +} + +impl PublicRoomJoinRule { + fn convert(value: ruma::directory::PublicRoomJoinRule) -> Option { + match value { + ruma::directory::PublicRoomJoinRule::Public => Some(Self::Public), + ruma::directory::PublicRoomJoinRule::Knock => Some(Self::Knock), + _ => None, + } + } +} + +#[derive(uniffi::Record)] +pub struct RoomDescription { + pub room_id: String, + pub name: Option, + pub topic: Option, + pub alias: Option, + pub avatar_url: Option, + pub join_rule: Option, + pub is_world_readable: bool, + pub joined_members: u64, +} + +impl From 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: PublicRoomJoinRule::convert(value.join_rule), + is_world_readable: value.is_world_readable, + joined_members: value.joined_members, + } + } +} + +#[derive(uniffi::Object)] +pub struct RoomDirectorySearch { + pub(crate) inner: SdkRoomDirectorySearch, +} + +// #[uniffi::export(async_runtime = "tokio")] +// impl RoomDirectorySearch { +// pub async fn next_page(self) -> Result<(), ClientError> { +// self.inner.next_page().await.map_err(Into::into) +// } +// } diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 844f12df934..be85138c25c 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -21,7 +21,7 @@ use ruma::{ OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, }; -use crate::Client; +use crate::{Client, Result}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct RoomDescription { @@ -56,18 +56,18 @@ impl RoomDirectorySearch { } } - pub async fn search(&mut self, filter: Option, batch_size: u32) { + pub async fn search(&mut self, filter: Option, batch_size: u32) -> Result<()> { self.filter = filter; self.batch_size = batch_size; self.next_token = None; self.results.clear(); self.is_at_last_page = false; - self.next_page().await; + self.next_page().await } - pub async fn next_page(&mut self) { + pub async fn next_page(&mut self) -> Result<()> { if self.is_at_last_page { - return; + return Ok(()); } let mut filter = Filter::new(); filter.generic_search_term = self.filter.clone(); @@ -76,28 +76,28 @@ impl RoomDirectorySearch { request.filter = filter; request.limit = Some(self.batch_size.into()); request.since = self.next_token.clone(); - if let Ok(response) = self.client.public_rooms_filtered(request).await { - self.next_token = response.next_batch; - if self.next_token.is_none() { - self.is_at_last_page = true; - } - self.results.append( - response - .chunk - .into_iter() - .map(|room| RoomDescription { - room_id: room.room_id, - name: room.name, - topic: room.topic, - alias: room.canonical_alias, - avatar_url: room.avatar_url, - join_rule: room.join_rule, - is_world_readable: room.world_readable, - joined_members: room.num_joined_members.into(), - }) - .collect(), - ); + let response = self.client.public_rooms_filtered(request).await?; + self.next_token = response.next_batch; + if self.next_token.is_none() { + self.is_at_last_page = true; } + self.results.append( + response + .chunk + .into_iter() + .map(|room| RoomDescription { + room_id: room.room_id, + name: room.name, + topic: room.topic, + alias: room.canonical_alias, + avatar_url: room.avatar_url, + join_rule: room.join_rule, + is_world_readable: room.world_readable, + joined_members: room.num_joined_members.into(), + }) + .collect(), + ); + Ok(()) } pub fn results(&self) -> impl Stream> { @@ -110,4 +110,8 @@ impl RoomDirectorySearch { } self.results.len() / self.batch_size as usize } + + pub fn is_at_last_page(&self) -> bool { + self.is_at_last_page + } } diff --git a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs index 0bfc61caa27..5eaf5e262c5 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs @@ -20,68 +20,54 @@ use matrix_sdk::{ room_directory_search::RoomDirectorySearch, ruma::api::client::room::{create_room::v3::Request as CreateRoomRequest, Visibility}, }; +use rand::{thread_rng, Rng}; use stream_assert::{assert_next_eq, assert_pending}; use tracing::warn; use crate::helpers::TestClientBuilder; #[tokio::test(flavor = "multi_thread")] -async fn test_room_directory_search_no_filter() -> Result<()> { +async fn test_room_directory_search_filter() -> Result<()> { let alice = TestClientBuilder::new("alice".to_owned()).use_sqlite().build().await?; - for index in 0..8 { + let search_string = random_string(32); + for index in 0..25 { let mut request: CreateRoomRequest = CreateRoomRequest::new(); request.visibility = Visibility::Public; - let name = format!("test_room_{}", index); - request.name = Some(name); + request.name = Some(format!("{}{}", search_string, index)); alice.create_room(request).await?; } let mut room_directory_search = RoomDirectorySearch::new(alice); let mut stream = room_directory_search.results(); - room_directory_search.search(None, 5).await; + room_directory_search.search(Some(search_string), 10).await?; assert_next_eq!(stream, VectorDiff::Clear); - if let VectorDiff::Append { values } = stream.next().now_or_never().unwrap().unwrap() { - warn!("Values: {:?}", values); - assert_eq!(values.len(), 5); - } else { - panic!("Expected a Vector::Append"); + + for _ in 0..2 { + if let VectorDiff::Append { values } = stream.next().now_or_never().unwrap().unwrap() { + warn!("Values: {:?}", values); + assert_eq!(values.len(), 10); + } else { + panic!("Expected a Vector::Append"); + } + assert_pending!(stream); + room_directory_search.next_page().await?; } - assert_pending!(stream); - room_directory_search.next_page().await; if let VectorDiff::Append { values } = stream.next().now_or_never().unwrap().unwrap() { warn!("Values: {:?}", values); - assert_eq!(values.len(), 3); + assert_eq!(values.len(), 5); } else { panic!("Expected a Vector::Append"); } assert_pending!(stream); - - room_directory_search.next_page().await; + room_directory_search.next_page().await?; assert_pending!(stream); Ok(()) } -#[tokio::test(flavor = "multi_thread")] -async fn test_room_directory_search_filter() -> Result<()> { - let alice = TestClientBuilder::new("alice".to_owned()).use_sqlite().build().await?; - let mut request: CreateRoomRequest = CreateRoomRequest::new(); - request.visibility = Visibility::Public; - alice.create_room(request).await?; - - let mut request: CreateRoomRequest = CreateRoomRequest::new(); - request.visibility = Visibility::Public; - request.name = Some("test".to_owned()); - alice.create_room(request).await?; - - let mut room_directory_search = RoomDirectorySearch::new(alice); - let mut stream = room_directory_search.results(); - room_directory_search.search(Some("test".to_owned()), 10).await; - assert_next_eq!(stream, VectorDiff::Clear); - if let VectorDiff::Append { values } = stream.next().now_or_never().unwrap().unwrap() { - assert_eq!(values.len(), 1); - } else { - panic!("Expected a Vector::Append"); - } - assert_pending!(stream); - Ok(()) +fn random_string(length: usize) -> String { + thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(length) + .map(char::from) + .collect() } From d9231be1bad94722b8a39c415710c154a4999ae8 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 27 Feb 2024 17:08:55 +0100 Subject: [PATCH 05/30] feat(bindings): listener code --- bindings/matrix-sdk-ffi/src/client.rs | 8 +- .../src/room_directory_search.rs | 88 +++++++++++++++++-- .../matrix-sdk/src/room_directory_search.rs | 2 +- 3 files changed, 84 insertions(+), 14 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index cde3c488a2c..7e9a4fa8a07 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -743,11 +743,9 @@ impl Client { } pub fn room_directory_search(&self) -> Arc { - Arc::new(RoomDirectorySearch { - inner: matrix_sdk::room_directory_search::RoomDirectorySearch::new( - (*self.inner).clone(), - ), - }) + Arc::new(RoomDirectorySearch::new( + matrix_sdk::room_directory_search::RoomDirectorySearch::new((*self.inner).clone()), + )) } } diff --git a/bindings/matrix-sdk-ffi/src/room_directory_search.rs b/bindings/matrix-sdk-ffi/src/room_directory_search.rs index 11dff161984..80e7d30ad02 100644 --- a/bindings/matrix-sdk-ffi/src/room_directory_search.rs +++ b/bindings/matrix-sdk-ffi/src/room_directory_search.rs @@ -13,9 +13,15 @@ // 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::{pin_mut, StreamExt}; use matrix_sdk::{room_directory_search::RoomDirectorySearch as SdkRoomDirectorySearch, Client}; +use tokio::sync::RwLock; -use crate::error::ClientError; +use super::RUNTIME; +use crate::{error::ClientError, task_handle::TaskHandle}; #[derive(uniffi::Enum)] pub enum PublicRoomJoinRule { @@ -62,12 +68,78 @@ impl From for RoomDescriptio #[derive(uniffi::Object)] pub struct RoomDirectorySearch { - pub(crate) inner: SdkRoomDirectorySearch, + pub(crate) inner: RwLock, +} + +impl RoomDirectorySearch { + pub fn new(inner: SdkRoomDirectorySearch) -> Self { + Self { inner: RwLock::new(inner) } + } +} + +#[uniffi::export(async_runtime = "tokio")] +impl RoomDirectorySearch { + pub async fn next_page(&self) -> Result<(), ClientError> { + let mut inner = self.inner.write().await; + inner.next_page().await?; + Ok(()) + } + + pub async fn search(&self, filter: Option, batch_size: u32) -> Result<(), ClientError> { + let mut inner = self.inner.write().await; + inner.search(filter, batch_size).await?; + Ok(()) + } + + pub async fn loaded_pages(&self) -> Result { + let inner = self.inner.read().await; + Ok(inner.loaded_pages() as u32) + } + + pub async fn is_at_last_page(&self) -> Result { + let inner = self.inner.read().await; + Ok(inner.is_at_last_page()) + } + + pub async fn entries( + &self, + listener: Box, + ) -> RoomDirectorySearchEntriesResult { + let entries_stream = self.inner.read().await.results(); + RoomDirectorySearchEntriesResult { + entries_stream: Arc::new(TaskHandle::new(RUNTIME.spawn(async move { + pin_mut!(entries_stream); + + while let Some(diff) = entries_stream.next().await { + match diff { + VectorDiff::Clear => { + listener.on_update(RoomDirectorySearchEntryUpdate::Clear); + } + VectorDiff::Append { values } => { + listener.on_update(RoomDirectorySearchEntryUpdate::Append { + values: values.into_iter().map(|value| value.into()).collect(), + }); + } + _ => {} + } + } + }))), + } + } +} + +#[derive(uniffi::Record)] +pub struct RoomDirectorySearchEntriesResult { + pub entries_stream: Arc, +} + +#[derive(uniffi::Enum)] +pub enum RoomDirectorySearchEntryUpdate { + Clear, + Append { values: Vec }, } -// #[uniffi::export(async_runtime = "tokio")] -// impl RoomDirectorySearch { -// pub async fn next_page(self) -> Result<(), ClientError> { -// self.inner.next_page().await.map_err(Into::into) -// } -// } +#[uniffi::export(callback_interface)] +pub trait RoomDirectorySearchEntriesListener: Send + Sync + Debug { + fn on_update(&self, room_entries_update: RoomDirectorySearchEntryUpdate); +} diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index be85138c25c..1ab090a73e5 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use eyeball_im::{ObservableVector, VectorDiff}; +use eyeball_im::{ObservableVector, VectorDiff, VectorSubscriber}; use futures_core::Stream; use ruma::{ api::client::directory::get_public_rooms_filtered::v3::Request as PublicRoomsFilterRequest, From caa9a7d8be135ba0506a906ffd19e174337eb6e4 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 27 Feb 2024 17:31:26 +0100 Subject: [PATCH 06/30] tests: code improvement for the filter integration test --- .../src/tests/room_directory_search.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs index 5eaf5e262c5..a2e8fdb4a6d 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs @@ -33,7 +33,9 @@ async fn test_room_directory_search_filter() -> Result<()> { for index in 0..25 { let mut request: CreateRoomRequest = CreateRoomRequest::new(); request.visibility = Visibility::Public; - request.name = Some(format!("{}{}", search_string, index)); + let name = format!("{}_{}", search_string, index); + warn!("room name: {}", name); + request.name = Some(name); alice.create_room(request).await?; } let mut room_directory_search = RoomDirectorySearch::new(alice); @@ -61,6 +63,17 @@ async fn test_room_directory_search_filter() -> Result<()> { assert_pending!(stream); room_directory_search.next_page().await?; assert_pending!(stream); + + // This should reset the state completely + room_directory_search.search(None, 25).await?; + assert_next_eq!(stream, VectorDiff::Clear); + if let VectorDiff::Append { values } = stream.next().now_or_never().unwrap().unwrap() { + warn!("Values: {:?}", values); + assert_eq!(values.len(), 25); + } else { + panic!("Expected a Vector::Append"); + } + assert_pending!(stream); Ok(()) } From 37d95571e992f969e6cc9f6d46fd50dc0220867a Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 27 Feb 2024 17:40:03 +0100 Subject: [PATCH 07/30] test: fixed a test by a adding a small delay --- .../src/tests/room_directory_search.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs index a2e8fdb4a6d..eafab919bf5 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::time::Duration; + use anyhow::Result; use eyeball_im::VectorDiff; use futures::{FutureExt, StreamExt}; @@ -22,6 +24,7 @@ use matrix_sdk::{ }; use rand::{thread_rng, Rng}; use stream_assert::{assert_next_eq, assert_pending}; +use tokio::time::sleep; use tracing::warn; use crate::helpers::TestClientBuilder; @@ -38,6 +41,7 @@ async fn test_room_directory_search_filter() -> Result<()> { request.name = Some(name); alice.create_room(request).await?; } + sleep(Duration::from_secs(1)).await; let mut room_directory_search = RoomDirectorySearch::new(alice); let mut stream = room_directory_search.results(); room_directory_search.search(Some(search_string), 10).await?; From 9c33540af8b80dd28532c73e9ede2796745423ce Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Wed, 28 Feb 2024 16:18:35 +0100 Subject: [PATCH 08/30] tests: improved tests and added a unit test --- .../matrix-sdk/src/room_directory_search.rs | 100 ++++++++++++++---- .../src/tests/room_directory_search.rs | 43 +++----- 2 files changed, 97 insertions(+), 46 deletions(-) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 1ab090a73e5..a4d5bc8f332 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -13,8 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use eyeball_im::{ObservableVector, VectorDiff, VectorSubscriber}; +use eyeball_im::{ObservableVector, VectorDiff}; use futures_core::Stream; +use imbl::Vector; use ruma::{ api::client::directory::get_public_rooms_filtered::v3::Request as PublicRoomsFilterRequest, directory::{Filter, PublicRoomJoinRule}, @@ -35,6 +36,21 @@ pub struct RoomDescription { pub joined_members: u64, } +impl From for RoomDescription { + fn from(value: ruma::directory::PublicRoomsChunk) -> Self { + Self { + room_id: value.room_id, + name: value.name, + topic: value.topic, + alias: value.canonical_alias, + avatar_url: value.avatar_url, + join_rule: value.join_rule, + is_world_readable: value.world_readable, + joined_members: value.num_joined_members.into(), + } + } +} + pub struct RoomDirectorySearch { batch_size: u32, filter: Option, @@ -81,27 +97,14 @@ impl RoomDirectorySearch { if self.next_token.is_none() { self.is_at_last_page = true; } - self.results.append( - response - .chunk - .into_iter() - .map(|room| RoomDescription { - room_id: room.room_id, - name: room.name, - topic: room.topic, - alias: room.canonical_alias, - avatar_url: room.avatar_url, - join_rule: room.join_rule, - is_world_readable: room.world_readable, - joined_members: room.num_joined_members.into(), - }) - .collect(), - ); + self.results.append(response.chunk.into_iter().map(Into::into).collect()); Ok(()) } - pub fn results(&self) -> impl Stream> { - self.results.subscribe().into_stream() + pub fn results( + &self, + ) -> (Vector, impl Stream>>) { + self.results.subscribe().into_values_and_batched_stream() } pub fn loaded_pages(&self) -> usize { @@ -115,3 +118,62 @@ impl RoomDirectorySearch { self.is_at_last_page } } + +#[cfg(test)] +mod tests { + use matrix_sdk_test::{async_test, test_json}; + use ruma::{OwnedRoomId, RoomAliasId, RoomId}; + use wiremock::{http::Method, Match, Mock, MockServer, Request, ResponseTemplate}; + + use crate::{ + room_directory_search::{RoomDescription, RoomDirectorySearch}, + test_utils::logged_in_client, + Client, + }; + + struct RoomDirectorySearchMatcher; + + impl Match for RoomDirectorySearchMatcher { + fn matches(&self, request: &Request) -> bool { + let match_url = request.url.path() == "/_matrix/client/v3/publicRooms"; + let match_method = request.method == Method::Post; + match_url && match_method + } + } + + #[async_test] + async fn search_success() { + let (server, client) = new_client().await; + + let mut room_directory_search = RoomDirectorySearch::new(client); + Mock::given(RoomDirectorySearchMatcher) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) + .mount(&server) + .await; + + room_directory_search.search(None, 1).await.unwrap(); + let (results, _) = room_directory_search.results(); + assert_eq!(results.len(), 1); + assert_eq!( + results[0], + RoomDescription { + room_id: RoomId::parse("!ol19s:bleecker.street").unwrap(), + name: Some("CHEESE".into()), + topic: Some("Tasty tasty cheese".into()), + alias: RoomAliasId::parse("#room:example.com").unwrap().into(), + avatar_url: Some("mxc://bleeker.street/CHEDDARandBRIE".into()), + join_rule: ruma::directory::PublicRoomJoinRule::Public, + is_world_readable: true, + joined_members: 37, + } + ); + assert_eq!(room_directory_search.is_at_last_page, false); + assert_eq!(room_directory_search.loaded_pages(), 1); + } + + async fn new_client() -> (MockServer, Client) { + let server = MockServer::start().await; + let client = logged_in_client(Some(server.uri())).await; + (server, client) + } +} diff --git a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs index eafab919bf5..e21bcb4bbc9 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs @@ -16,8 +16,9 @@ use std::time::Duration; use anyhow::Result; +use assert_matches::assert_matches; use eyeball_im::VectorDiff; -use futures::{FutureExt, StreamExt}; +use futures::StreamExt; use matrix_sdk::{ room_directory_search::RoomDirectorySearch, ruma::api::client::room::{create_room::v3::Request as CreateRoomRequest, Visibility}, @@ -43,40 +44,28 @@ async fn test_room_directory_search_filter() -> Result<()> { } sleep(Duration::from_secs(1)).await; let mut room_directory_search = RoomDirectorySearch::new(alice); - let mut stream = room_directory_search.results(); + let (values, mut stream) = room_directory_search.results(); + assert!(values.is_empty()); room_directory_search.search(Some(search_string), 10).await?; - assert_next_eq!(stream, VectorDiff::Clear); + let results_batch = stream.next().await.unwrap(); + assert_matches!(&results_batch[0], VectorDiff::Clear); + assert_matches!(&results_batch[1], VectorDiff::Append { values } => { assert_eq!(values.len(), 10); }); - for _ in 0..2 { - if let VectorDiff::Append { values } = stream.next().now_or_never().unwrap().unwrap() { - warn!("Values: {:?}", values); - assert_eq!(values.len(), 10); - } else { - panic!("Expected a Vector::Append"); - } - assert_pending!(stream); - room_directory_search.next_page().await?; - } - - if let VectorDiff::Append { values } = stream.next().now_or_never().unwrap().unwrap() { - warn!("Values: {:?}", values); - assert_eq!(values.len(), 5); - } else { - panic!("Expected a Vector::Append"); - } + room_directory_search.next_page().await?; + room_directory_search.next_page().await?; + let results_batch = stream.next().await.unwrap(); + assert_eq!(results_batch.len(), 2); + assert_matches!(&results_batch[0], VectorDiff::Append { values } => { assert_eq!(values.len(), 10); }); + assert_matches!(&results_batch[1], VectorDiff::Append { values } => { assert_eq!(values.len(), 5); }); assert_pending!(stream); room_directory_search.next_page().await?; assert_pending!(stream); // This should reset the state completely room_directory_search.search(None, 25).await?; - assert_next_eq!(stream, VectorDiff::Clear); - if let VectorDiff::Append { values } = stream.next().now_or_never().unwrap().unwrap() { - warn!("Values: {:?}", values); - assert_eq!(values.len(), 25); - } else { - panic!("Expected a Vector::Append"); - } + let results_batch = stream.next().await.unwrap(); + assert_matches!(&results_batch[0], VectorDiff::Clear); + assert_matches!(&results_batch[1], VectorDiff::Append { values } => { assert_eq!(values.len(), 25); }); assert_pending!(stream); Ok(()) } From 690ed4611da9b5a83e39894b9a051bea99ac1241 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Wed, 28 Feb 2024 19:01:42 +0100 Subject: [PATCH 09/30] tests: unit tests have been completed --- .../matrix-sdk/src/room_directory_search.rs | 203 +++++++++++++++--- .../src/test_json/api_responses.rs | 23 ++ testing/matrix-sdk-test/src/test_json/mod.rs | 4 +- 3 files changed, 204 insertions(+), 26 deletions(-) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index a4d5bc8f332..9014d531f8c 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -122,8 +122,12 @@ impl RoomDirectorySearch { #[cfg(test)] mod tests { use matrix_sdk_test::{async_test, test_json}; - use ruma::{OwnedRoomId, RoomAliasId, RoomId}; - use wiremock::{http::Method, Match, Mock, MockServer, Request, ResponseTemplate}; + use ruma::{directory::Filter, serde::Raw, RoomAliasId, RoomId}; + use serde_json::Value as JsonValue; + use wiremock::{ + matchers::{method, path_regex}, + Match, Mock, MockServer, Request, ResponseTemplate, + }; use crate::{ room_directory_search::{RoomDescription, RoomDirectorySearch}, @@ -131,22 +135,77 @@ mod tests { Client, }; - struct RoomDirectorySearchMatcher; + struct RoomDirectorySearchMatcher { + next_token: Option, + filter_term: Option, + } impl Match for RoomDirectorySearchMatcher { fn matches(&self, request: &Request) -> bool { - let match_url = request.url.path() == "/_matrix/client/v3/publicRooms"; - let match_method = request.method == Method::Post; - match_url && match_method + let Ok(body) = request.body_json::>() else { + return false; + }; + + // The body's `since` field is set equal to the matcher's next_token. + if !body.get_field::("since").is_ok_and(|s| s == self.next_token) { + return false; + } + + // The body's `filter` field has `generic_search_term` equal to the matcher's + // next_token. + if !body.get_field::("filter").is_ok_and(|s| { + if self.filter_term.is_none() { + s.is_none() || s.is_some_and(|s| s.generic_search_term.is_none()) + } else { + s.is_some_and(|s| s.generic_search_term == self.filter_term) + } + }) { + return false; + } + + method("POST").matches(request) + && path_regex("/_matrix/client/../publicRooms").matches(request) + } + } + + fn get_first_page_description() -> RoomDescription { + RoomDescription { + room_id: RoomId::parse("!ol19s:bleecker.street").unwrap(), + name: Some("CHEESE".into()), + topic: Some("Tasty tasty cheese".into()), + alias: None, + avatar_url: Some("mxc://bleeker.street/CHEDDARandBRIE".into()), + join_rule: ruma::directory::PublicRoomJoinRule::Public, + is_world_readable: true, + joined_members: 37, } } + fn get_second_page_description() -> RoomDescription { + RoomDescription { + room_id: RoomId::parse("!ca18r:bleecker.street").unwrap(), + name: Some("PEAR".into()), + topic: Some("Tasty tasty pear".into()), + alias: RoomAliasId::parse("#murrays:pear.bar").ok(), + avatar_url: Some("mxc://bleeker.street/pear".into()), + join_rule: ruma::directory::PublicRoomJoinRule::Knock, + is_world_readable: false, + joined_members: 20, + } + } + + async fn new_server_and_client() -> (MockServer, Client) { + let server = MockServer::start().await; + let client = logged_in_client(Some(server.uri())).await; + (server, client) + } + #[async_test] async fn search_success() { - let (server, client) = new_client().await; + let (server, client) = new_server_and_client().await; let mut room_directory_search = RoomDirectorySearch::new(client); - Mock::given(RoomDirectorySearchMatcher) + Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None }) .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) .mount(&server) .await; @@ -154,26 +213,122 @@ mod tests { room_directory_search.search(None, 1).await.unwrap(); let (results, _) = room_directory_search.results(); assert_eq!(results.len(), 1); + assert_eq!(results[0], get_first_page_description()); + assert!(!room_directory_search.is_at_last_page); + assert_eq!(room_directory_search.loaded_pages(), 1); + } + + #[async_test] + async fn search_success_paginated() { + let (server, client) = new_server_and_client().await; + + let mut room_directory_search = RoomDirectorySearch::new(client); + Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None }) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) + .mount(&server) + .await; + + room_directory_search.search(None, 1).await.unwrap(); + + Mock::given(RoomDirectorySearchMatcher { + next_token: Some("p190q".into()), + filter_term: None, + }) + .respond_with( + ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS_FINAL_PAGE), + ) + .mount(&server) + .await; + + room_directory_search.next_page().await.unwrap(); + + let (results, _) = room_directory_search.results(); assert_eq!( - results[0], - RoomDescription { - room_id: RoomId::parse("!ol19s:bleecker.street").unwrap(), - name: Some("CHEESE".into()), - topic: Some("Tasty tasty cheese".into()), - alias: RoomAliasId::parse("#room:example.com").unwrap().into(), - avatar_url: Some("mxc://bleeker.street/CHEDDARandBRIE".into()), - join_rule: ruma::directory::PublicRoomJoinRule::Public, - is_world_readable: true, - joined_members: 37, - } + results, + vec![get_first_page_description(), get_second_page_description()].into() ); - assert_eq!(room_directory_search.is_at_last_page, false); + assert!(room_directory_search.is_at_last_page); + assert_eq!(room_directory_search.loaded_pages(), 2); + } + + #[async_test] + async fn search_fails() { + let (server, client) = new_server_and_client().await; + + let mut room_directory_search = RoomDirectorySearch::new(client); + Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None }) + .respond_with(ResponseTemplate::new(404)) + .mount(&server) + .await; + + room_directory_search.search(None, 1).await.unwrap_err(); + let (results, _) = room_directory_search.results(); + assert_eq!(results.len(), 0); + assert!(!room_directory_search.is_at_last_page); + assert_eq!(room_directory_search.loaded_pages(), 0); + } + + #[async_test] + async fn search_fails_when_paginating() { + let (server, client) = new_server_and_client().await; + + let mut room_directory_search = RoomDirectorySearch::new(client); + Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None }) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) + .mount(&server) + .await; + + room_directory_search.search(None, 1).await.unwrap(); + + Mock::given(RoomDirectorySearchMatcher { + next_token: Some("p190q".into()), + filter_term: None, + }) + .respond_with(ResponseTemplate::new(404)) + .mount(&server) + .await; + + room_directory_search.next_page().await.unwrap_err(); + + let (results, _) = room_directory_search.results(); + assert_eq!(results, vec![get_first_page_description()].into()); + assert!(!room_directory_search.is_at_last_page); assert_eq!(room_directory_search.loaded_pages(), 1); } - async fn new_client() -> (MockServer, Client) { - let server = MockServer::start().await; - let client = logged_in_client(Some(server.uri())).await; - (server, client) + #[async_test] + async fn search_success_paginated_with_filter() { + let (server, client) = new_server_and_client().await; + + let mut room_directory_search = RoomDirectorySearch::new(client); + Mock::given(RoomDirectorySearchMatcher { + next_token: None, + filter_term: Some("bleecker.street".into()), + }) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) + .mount(&server) + .await; + + room_directory_search.search(Some("bleecker.street".into()), 1).await.unwrap(); + + Mock::given(RoomDirectorySearchMatcher { + next_token: Some("p190q".into()), + filter_term: Some("bleecker.street".into()), + }) + .respond_with( + ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS_FINAL_PAGE), + ) + .mount(&server) + .await; + + room_directory_search.next_page().await.unwrap(); + + let (results, _) = room_directory_search.results(); + assert_eq!( + results, + vec![get_first_page_description(), get_second_page_description()].into() + ); + assert!(room_directory_search.is_at_last_page); + assert_eq!(room_directory_search.loaded_pages(), 2); } } diff --git a/testing/matrix-sdk-test/src/test_json/api_responses.rs b/testing/matrix-sdk-test/src/test_json/api_responses.rs index d534697717a..230022ba462 100644 --- a/testing/matrix-sdk-test/src/test_json/api_responses.rs +++ b/testing/matrix-sdk-test/src/test_json/api_responses.rs @@ -239,6 +239,7 @@ pub static NOT_FOUND: Lazy = Lazy::new(|| { }); /// `GET /_matrix/client/v3/publicRooms` +/// `POST /_matrix/client/v3/publicRooms` pub static PUBLIC_ROOMS: Lazy = Lazy::new(|| { json!({ "chunk": [ @@ -261,6 +262,28 @@ pub static PUBLIC_ROOMS: Lazy = Lazy::new(|| { }) }); +/// `GET /_matrix/client/v3/publicRooms` +/// `POST /_matrix/client/v3/publicRooms`` +pub static PUBLIC_ROOMS_FINAL_PAGE: Lazy = Lazy::new(|| { + json!({ + "chunk": [ + { + "canonical_alias": "#murrays:pear.bar", + "avatar_url": "mxc://bleeker.street/pear", + "guest_can_join": false, + "name": "PEAR", + "num_joined_members": 20, + "room_id": "!ca18r:bleecker.street", + "topic": "Tasty tasty pear", + "world_readable": false, + "join_rule": "knock" + } + ], + "prev_batch": "p190q", + "total_room_count_estimate": 115 + }) +}); + /// `POST /_matrix/client/v3/refresh` without new refresh token. pub static REFRESH_TOKEN: Lazy = Lazy::new(|| { json!({ diff --git a/testing/matrix-sdk-test/src/test_json/mod.rs b/testing/matrix-sdk-test/src/test_json/mod.rs index e5a6a5255e1..9c29bba81a1 100644 --- a/testing/matrix-sdk-test/src/test_json/mod.rs +++ b/testing/matrix-sdk-test/src/test_json/mod.rs @@ -18,8 +18,8 @@ pub mod sync_events; pub use api_responses::{ DEVICES, GET_ALIAS, KEYS_QUERY, KEYS_QUERY_TWO_DEVICES_ONE_SIGNED, KEYS_UPLOAD, LOGIN, LOGIN_RESPONSE_ERR, LOGIN_TYPES, LOGIN_WITH_DISCOVERY, LOGIN_WITH_REFRESH_TOKEN, NOT_FOUND, - PUBLIC_ROOMS, REFRESH_TOKEN, REFRESH_TOKEN_WITH_REFRESH_TOKEN, REGISTRATION_RESPONSE_ERR, - UNKNOWN_TOKEN_SOFT_LOGOUT, VERSIONS, WELL_KNOWN, WHOAMI, + PUBLIC_ROOMS, PUBLIC_ROOMS_FINAL_PAGE, REFRESH_TOKEN, REFRESH_TOKEN_WITH_REFRESH_TOKEN, + REGISTRATION_RESPONSE_ERR, UNKNOWN_TOKEN_SOFT_LOGOUT, VERSIONS, WELL_KNOWN, WHOAMI, }; pub use members::MEMBERS; pub use sync::{ From 3e35f163b7971db79e9b38f573c3902a6a2e9ed2 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Wed, 28 Feb 2024 19:17:58 +0100 Subject: [PATCH 10/30] docs: updated documentation and removed code from ffi that will be changed soon --- .../src/room_directory_search.rs | 37 ++++++++++--------- .../matrix-sdk/src/room_directory_search.rs | 15 ++++++++ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/room_directory_search.rs b/bindings/matrix-sdk-ffi/src/room_directory_search.rs index 80e7d30ad02..3035165dd6f 100644 --- a/bindings/matrix-sdk-ffi/src/room_directory_search.rs +++ b/bindings/matrix-sdk-ffi/src/room_directory_search.rs @@ -16,8 +16,8 @@ use std::{fmt::Debug, sync::Arc}; use eyeball_im::VectorDiff; -use futures_util::{pin_mut, StreamExt}; -use matrix_sdk::{room_directory_search::RoomDirectorySearch as SdkRoomDirectorySearch, Client}; +use futures_util::pin_mut; +use matrix_sdk::room_directory_search::RoomDirectorySearch as SdkRoomDirectorySearch; use tokio::sync::RwLock; use super::RUNTIME; @@ -108,21 +108,24 @@ impl RoomDirectorySearch { let entries_stream = self.inner.read().await.results(); RoomDirectorySearchEntriesResult { entries_stream: Arc::new(TaskHandle::new(RUNTIME.spawn(async move { - pin_mut!(entries_stream); - - while let Some(diff) = entries_stream.next().await { - match diff { - VectorDiff::Clear => { - listener.on_update(RoomDirectorySearchEntryUpdate::Clear); - } - VectorDiff::Append { values } => { - listener.on_update(RoomDirectorySearchEntryUpdate::Append { - values: values.into_iter().map(|value| value.into()).collect(), - }); - } - _ => {} - } - } + // TODO: This needs to get improved with sensei Ivan + // pin_mut!(entries_stream); + + // while let Some(diff) = entries_stream.next().await { + // match diff { + // VectorDiff::Clear => { + // + // listener.on_update(RoomDirectorySearchEntryUpdate::Clear); + // } + // VectorDiff::Append { values } => { + // + // listener.on_update(RoomDirectorySearchEntryUpdate::Append { + // values: values.into_iter().map(|value| + // value.into()).collect(), }); + // } + // _ => {} + // } + // } }))), } } diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 9014d531f8c..3c7781fe0fb 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -72,6 +72,14 @@ impl RoomDirectorySearch { } } + /// 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 + /// + /// This method will clear the current search results and start a new one + /// Should never be used concurrently with another `next_page` or a + /// `search`. pub async fn search(&mut self, filter: Option, batch_size: u32) -> Result<()> { self.filter = filter; self.batch_size = batch_size; @@ -81,6 +89,9 @@ impl RoomDirectorySearch { self.next_page().await } + /// Asks the server for the next page of the current search + /// Should never be used concurrently with another `next_page` or a + /// `search`. pub async fn next_page(&mut self) -> Result<()> { if self.is_at_last_page { return Ok(()); @@ -101,12 +112,15 @@ impl RoomDirectorySearch { Ok(()) } + /// Get the initial value of the current stored room descriptions in the + /// search, and a stream of updates for them. pub fn results( &self, ) -> (Vector, impl Stream>>) { self.results.subscribe().into_values_and_batched_stream() } + /// Get the number of pages that have been loaded so far pub fn loaded_pages(&self) -> usize { if self.batch_size == 0 { return 0; @@ -114,6 +128,7 @@ impl RoomDirectorySearch { self.results.len() / self.batch_size as usize } + /// Get whether the search is at the last page pub fn is_at_last_page(&self) -> bool { self.is_at_last_page } From 70466aafb4e1de630e2ef0811e3ba4fd88673525 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Wed, 28 Feb 2024 19:25:52 +0100 Subject: [PATCH 11/30] docs: more documentation for types and the mod --- crates/matrix-sdk/src/room_directory_search.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 3c7781fe0fb..49921e921c6 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Types for searching the public room directory. + use eyeball_im::{ObservableVector, VectorDiff}; use futures_core::Stream; use imbl::Vector; @@ -24,15 +26,24 @@ use ruma::{ use crate::{Client, Result}; +/// This struct represents a single result of a room directory search. #[derive(Clone, Debug, Eq, PartialEq)] pub struct RoomDescription { + /// The room's ID. pub room_id: OwnedRoomId, + /// The name of the room, if any. pub name: Option, + /// The topic of the room, if any. pub topic: Option, + /// The canonical alias of the room, if any. pub alias: Option, + /// The room's avatar URL, if any. pub avatar_url: Option, + /// The room's join rule. pub join_rule: PublicRoomJoinRule, + /// Whether can be previewed pub is_world_readable: bool, + /// The number of members that have joined the room. pub joined_members: u64, } @@ -51,6 +62,7 @@ impl From for RoomDescription { } } +#[derive(Debug)] pub struct RoomDirectorySearch { batch_size: u32, filter: Option, From 4dd7c3093c43d1f5c00b46fb4f5409e1d55574b3 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Wed, 28 Feb 2024 19:41:31 +0100 Subject: [PATCH 12/30] tests: improved the tests by adding the limit into the check for the request --- crates/matrix-sdk/src/room_directory_search.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 49921e921c6..7ffff43cfd4 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -165,6 +165,7 @@ mod tests { struct RoomDirectorySearchMatcher { next_token: Option, filter_term: Option, + limit: u32, } impl Match for RoomDirectorySearchMatcher { @@ -178,6 +179,10 @@ mod tests { return false; } + if !body.get_field::("limit").is_ok_and(|s| s == Some(self.limit)) { + return false; + } + // The body's `filter` field has `generic_search_term` equal to the matcher's // next_token. if !body.get_field::("filter").is_ok_and(|s| { @@ -232,7 +237,7 @@ mod tests { let (server, client) = new_server_and_client().await; let mut room_directory_search = RoomDirectorySearch::new(client); - Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None }) + Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None, limit: 1 }) .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) .mount(&server) .await; @@ -250,7 +255,7 @@ mod tests { let (server, client) = new_server_and_client().await; let mut room_directory_search = RoomDirectorySearch::new(client); - Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None }) + Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None, limit: 1 }) .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) .mount(&server) .await; @@ -260,6 +265,7 @@ mod tests { Mock::given(RoomDirectorySearchMatcher { next_token: Some("p190q".into()), filter_term: None, + limit: 1, }) .respond_with( ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS_FINAL_PAGE), @@ -283,7 +289,7 @@ mod tests { let (server, client) = new_server_and_client().await; let mut room_directory_search = RoomDirectorySearch::new(client); - Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None }) + Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None, limit: 1 }) .respond_with(ResponseTemplate::new(404)) .mount(&server) .await; @@ -300,7 +306,7 @@ mod tests { let (server, client) = new_server_and_client().await; let mut room_directory_search = RoomDirectorySearch::new(client); - Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None }) + Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None, limit: 1 }) .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) .mount(&server) .await; @@ -310,6 +316,7 @@ mod tests { Mock::given(RoomDirectorySearchMatcher { next_token: Some("p190q".into()), filter_term: None, + limit: 1, }) .respond_with(ResponseTemplate::new(404)) .mount(&server) @@ -331,6 +338,7 @@ mod tests { Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: Some("bleecker.street".into()), + limit: 1, }) .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) .mount(&server) @@ -341,6 +349,7 @@ mod tests { Mock::given(RoomDirectorySearchMatcher { next_token: Some("p190q".into()), filter_term: Some("bleecker.street".into()), + limit: 1, }) .respond_with( ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS_FINAL_PAGE), From 4b1eefca80d96bf6577c4851a726fb623ac5c72b Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Thu, 29 Feb 2024 11:52:31 +0100 Subject: [PATCH 13/30] Apply suggestions from code review Co-authored-by: Ivan Enderlin Signed-off-by: Mauro <34335419+Velin92@users.noreply.github.com> --- .../src/room_directory_search.rs | 36 ++++++------------- .../matrix-sdk/src/room_directory_search.rs | 35 +++++++++++------- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/room_directory_search.rs b/bindings/matrix-sdk-ffi/src/room_directory_search.rs index 3035165dd6f..06822de56ad 100644 --- a/bindings/matrix-sdk-ffi/src/room_directory_search.rs +++ b/bindings/matrix-sdk-ffi/src/room_directory_search.rs @@ -101,33 +101,19 @@ impl RoomDirectorySearch { Ok(inner.is_at_last_page()) } - pub async fn entries( + pub async fn results( &self, listener: Box, - ) -> RoomDirectorySearchEntriesResult { - let entries_stream = self.inner.read().await.results(); - RoomDirectorySearchEntriesResult { - entries_stream: Arc::new(TaskHandle::new(RUNTIME.spawn(async move { - // TODO: This needs to get improved with sensei Ivan - // pin_mut!(entries_stream); - - // while let Some(diff) = entries_stream.next().await { - // match diff { - // VectorDiff::Clear => { - // - // listener.on_update(RoomDirectorySearchEntryUpdate::Clear); - // } - // VectorDiff::Append { values } => { - // - // listener.on_update(RoomDirectorySearchEntryUpdate::Append { - // values: values.into_iter().map(|value| - // value.into()).collect(), }); - // } - // _ => {} - // } - // } - }))), - } + ) -> TaskHandle { + let (initial_values, stream) = self.inner.read().await.results(); + + TaskHandle::new(RUNTIME.spawn(async move { + listener.on_update(vec![VectorDiff::Reset { values: initial_values }.map(Into::into)]); + + while let Some(diffs) = stream.next().await { + listener.on_update(diffs.into_iter().map(|diff| diff.map(Into::into)).collect()); + } + })) } } diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 7ffff43cfd4..a0fdf50b590 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -27,6 +27,8 @@ use ruma::{ use crate::{Client, Result}; /// This struct represents a single result of a room directory search. +/// +/// It's produced by [`RoomDirectorySearch::results`]. #[derive(Clone, Debug, Eq, PartialEq)] pub struct RoomDescription { /// The room's ID. @@ -84,12 +86,13 @@ impl RoomDirectorySearch { } } - /// Starts a filtered search for the server - /// If the `filter` is not provided it will search for all the rooms + /// 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 + /// per request. /// - /// This method will clear the current search results and start a new one + /// This method will clear the current search results and start a new one. /// Should never be used concurrently with another `next_page` or a /// `search`. pub async fn search(&mut self, filter: Option, batch_size: u32) -> Result<()> { @@ -101,7 +104,8 @@ impl RoomDirectorySearch { self.next_page().await } - /// Asks the server for the next page of the current search + /// Asks the server for the next page of the current search. + /// /// Should never be used concurrently with another `next_page` or a /// `search`. pub async fn next_page(&mut self) -> Result<()> { @@ -124,7 +128,7 @@ impl RoomDirectorySearch { Ok(()) } - /// Get the initial value of the current stored room descriptions in the + /// Get the initial values of the current stored room descriptions in the /// search, and a stream of updates for them. pub fn results( &self, @@ -132,12 +136,13 @@ impl RoomDirectorySearch { self.results.subscribe().into_values_and_batched_stream() } - /// Get the number of pages that have been loaded so far + /// Get the number of pages that have been loaded so far. pub fn loaded_pages(&self) -> usize { if self.batch_size == 0 { return 0; } - self.results.len() / self.batch_size as usize + + (self.results.len() / self.batch_size).ceil() as usize } /// Get whether the search is at the last page @@ -243,10 +248,11 @@ mod tests { .await; room_directory_search.search(None, 1).await.unwrap(); - let (results, _) = room_directory_search.results(); + let (results, stream) = room_directory_search.results(); + assert_pending!(stream); assert_eq!(results.len(), 1); assert_eq!(results[0], get_first_page_description()); - assert!(!room_directory_search.is_at_last_page); + assert!(!room_directory_search.is_at_last_page()); assert_eq!(room_directory_search.loaded_pages(), 1); } @@ -294,11 +300,14 @@ mod tests { .mount(&server) .await; - room_directory_search.search(None, 1).await.unwrap_err(); - let (results, _) = room_directory_search.results(); + let search = room_directory_search.search(None, 1).await; + assert!(search.is_err()); + + let (results, stream) = room_directory_search.results(); assert_eq!(results.len(), 0); - assert!(!room_directory_search.is_at_last_page); + assert!(!room_directory_search.is_at_last_page()); assert_eq!(room_directory_search.loaded_pages(), 0); + assert_pending!(stream); } #[async_test] From 2163ab03ec5cf265508572077c055004bf588a5d Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Thu, 29 Feb 2024 12:48:49 +0100 Subject: [PATCH 14/30] tests: test improvements and added a new test --- .../src/room_directory_search.rs | 38 ++++++++- .../matrix-sdk/src/room_directory_search.rs | 81 ++++++++++++++----- .../src/tests/room_directory_search.rs | 2 +- 3 files changed, 96 insertions(+), 25 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/room_directory_search.rs b/bindings/matrix-sdk-ffi/src/room_directory_search.rs index 06822de56ad..ea549b53090 100644 --- a/bindings/matrix-sdk-ffi/src/room_directory_search.rs +++ b/bindings/matrix-sdk-ffi/src/room_directory_search.rs @@ -16,6 +16,7 @@ use std::{fmt::Debug, sync::Arc}; use eyeball_im::VectorDiff; +use futures::StreamExt; use futures_util::pin_mut; use matrix_sdk::room_directory_search::RoomDirectorySearch as SdkRoomDirectorySearch; use tokio::sync::RwLock; @@ -106,10 +107,12 @@ impl RoomDirectorySearch { listener: Box, ) -> TaskHandle { let (initial_values, stream) = self.inner.read().await.results(); - + TaskHandle::new(RUNTIME.spawn(async move { - listener.on_update(vec![VectorDiff::Reset { values: initial_values }.map(Into::into)]); - + listener.on_update(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.map(Into::into)).collect()); } @@ -124,8 +127,35 @@ pub struct RoomDirectorySearchEntriesResult { #[derive(uniffi::Enum)] pub enum RoomDirectorySearchEntryUpdate { - Clear, Append { values: Vec }, + 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 }, +} + +impl From> for RoomDirectorySearchEntryUpdate { + fn from(diff: VectorDiff) -> Self { + match diff { + VectorDiff::Append { values } => Self::Append { values }, + VectorDiff::Clear => Self::Clear, + VectorDiff::PushFront { value } => Self::PushFront { value }, + VectorDiff::PushBack { value } => Self::PushBack { value }, + VectorDiff::PopFront => Self::PopFront, + VectorDiff::PopBack => Self::PopBack, + VectorDiff::Insert { index, value } => Self::Insert { index, value }, + VectorDiff::Set { index, value } => Self::Set { index, value }, + VectorDiff::Remove { index } => Self::Remove { index }, + VectorDiff::Truncate { length } => Self::Truncate { length }, + VectorDiff::Reset { values } => Self::Reset { values }, + } + } } #[uniffi::export(callback_interface)] diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index a0fdf50b590..b438ff6f29b 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -141,8 +141,7 @@ impl RoomDirectorySearch { if self.batch_size == 0 { return 0; } - - (self.results.len() / self.batch_size).ceil() as usize + (self.results.len() as f64 / self.batch_size as f64).ceil() as usize } /// Get whether the search is at the last page @@ -153,9 +152,13 @@ impl RoomDirectorySearch { #[cfg(test)] mod tests { + use assert_matches::assert_matches; + use eyeball_im::VectorDiff; + use futures_util::StreamExt; use matrix_sdk_test::{async_test, test_json}; use ruma::{directory::Filter, serde::Raw, RoomAliasId, RoomId}; use serde_json::Value as JsonValue; + use stream_assert::assert_pending; use wiremock::{ matchers::{method, path_regex}, Match, Mock, MockServer, Request, ResponseTemplate, @@ -248,7 +251,7 @@ mod tests { .await; room_directory_search.search(None, 1).await.unwrap(); - let (results, stream) = room_directory_search.results(); + let (results, mut stream) = room_directory_search.results(); assert_pending!(stream); assert_eq!(results.len(), 1); assert_eq!(results[0], get_first_page_description()); @@ -267,6 +270,10 @@ mod tests { .await; room_directory_search.search(None, 1).await.unwrap(); + let (initial_results, mut stream) = room_directory_search.results(); + assert_eq!(initial_results, vec![get_first_page_description()].into()); + assert!(!room_directory_search.is_at_last_page()); + assert_eq!(room_directory_search.loaded_pages(), 1); Mock::given(RoomDirectorySearchMatcher { next_token: Some("p190q".into()), @@ -281,13 +288,11 @@ mod tests { room_directory_search.next_page().await.unwrap(); - let (results, _) = room_directory_search.results(); - assert_eq!( - results, - vec![get_first_page_description(), get_second_page_description()].into() - ); - assert!(room_directory_search.is_at_last_page); + let results_batch: Vec> = stream.next().await.unwrap(); + assert_matches!(&results_batch[0], VectorDiff::Append { values } => { assert_eq!(values, &vec![get_second_page_description()].into()); }); + assert!(room_directory_search.is_at_last_page()); assert_eq!(room_directory_search.loaded_pages(), 2); + assert_pending!(stream); } #[async_test] @@ -300,10 +305,9 @@ mod tests { .mount(&server) .await; - let search = room_directory_search.search(None, 1).await; - assert!(search.is_err()); + assert!(room_directory_search.next_page().await.is_err()); - let (results, stream) = room_directory_search.results(); + let (results, mut stream) = room_directory_search.results(); assert_eq!(results.len(), 0); assert!(!room_directory_search.is_at_last_page()); assert_eq!(room_directory_search.loaded_pages(), 0); @@ -331,11 +335,11 @@ mod tests { .mount(&server) .await; - room_directory_search.next_page().await.unwrap_err(); + assert!(room_directory_search.next_page().await.is_err()); let (results, _) = room_directory_search.results(); assert_eq!(results, vec![get_first_page_description()].into()); - assert!(!room_directory_search.is_at_last_page); + assert!(!room_directory_search.is_at_last_page()); assert_eq!(room_directory_search.loaded_pages(), 1); } @@ -354,6 +358,10 @@ mod tests { .await; room_directory_search.search(Some("bleecker.street".into()), 1).await.unwrap(); + let (initial_results, mut stream) = room_directory_search.results(); + assert_eq!(initial_results, vec![get_first_page_description()].into()); + assert!(!room_directory_search.is_at_last_page()); + assert_eq!(room_directory_search.loaded_pages(), 1); Mock::given(RoomDirectorySearchMatcher { next_token: Some("p190q".into()), @@ -368,12 +376,45 @@ mod tests { room_directory_search.next_page().await.unwrap(); - let (results, _) = room_directory_search.results(); - assert_eq!( - results, - vec![get_first_page_description(), get_second_page_description()].into() - ); - assert!(room_directory_search.is_at_last_page); + let results_batch: Vec> = stream.next().await.unwrap(); + assert_matches!(&results_batch[0], VectorDiff::Append { values } => { assert_eq!(values, &vec![get_second_page_description()].into()); }); + assert!(room_directory_search.is_at_last_page()); assert_eq!(room_directory_search.loaded_pages(), 2); + assert_pending!(stream); + } + + #[async_test] + async fn search_followed_by_another_search_with_filter() { + let (server, client) = new_server_and_client().await; + + let mut room_directory_search = RoomDirectorySearch::new(client); + Mock::given(RoomDirectorySearchMatcher { next_token: None, filter_term: None, limit: 1 }) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) + .mount(&server) + .await; + + room_directory_search.search(None, 1).await.unwrap(); + let (initial_results, mut stream) = room_directory_search.results(); + assert_eq!(initial_results, vec![get_first_page_description()].into()); + assert!(!room_directory_search.is_at_last_page()); + assert_eq!(room_directory_search.loaded_pages(), 1); + + Mock::given(RoomDirectorySearchMatcher { + next_token: None, + filter_term: Some("bleecker.street".into()), + limit: 1, + }) + .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::PUBLIC_ROOMS)) + .mount(&server) + .await; + + room_directory_search.search(Some("bleecker.street".into()), 1).await.unwrap(); + + let results_batch: Vec> = stream.next().await.unwrap(); + assert_matches!(&results_batch[0], VectorDiff::Clear); + assert_matches!(&results_batch[1], VectorDiff::Append { values } => { assert_eq!(values, &vec![get_first_page_description()].into()); }); + assert!(!room_directory_search.is_at_last_page()); + assert_eq!(room_directory_search.loaded_pages(), 1); + assert_pending!(stream); } } diff --git a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs index e21bcb4bbc9..3348bd14303 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs @@ -47,7 +47,7 @@ async fn test_room_directory_search_filter() -> Result<()> { let (values, mut stream) = room_directory_search.results(); assert!(values.is_empty()); room_directory_search.search(Some(search_string), 10).await?; - let results_batch = stream.next().await.unwrap(); + let results_batch: Vec> = stream.next().await.unwrap(); assert_matches!(&results_batch[0], VectorDiff::Clear); assert_matches!(&results_batch[1], VectorDiff::Append { values } => { assert_eq!(values.len(), 10); }); From 2e3ced1fb2f2586ff4176467d927e60366788805 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Thu, 29 Feb 2024 12:58:34 +0100 Subject: [PATCH 15/30] docs: more documentation --- .../matrix-sdk/src/room_directory_search.rs | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index b438ff6f29b..e81a2a9bfbe 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -63,7 +63,24 @@ impl From for RoomDescription { } } } - +/// RoomDirectorySearch allows searching the public room directory, with the +/// capability of using a filter and a batch_size. This struct is also +/// responsible for keeping the current state of the search, and exposing an +/// update of stream of the results, reset the search, or ask for the next page. +/// +/// # Example +/// +/// ```rust +/// use matrix_sdk::room_directory_search::RoomDirectorySearch; +/// +/// # fn main() -> Result<()> { +/// let room_directory_search = RoomDirectorySearch(client); +/// room_directory_search.search(None, 10).await?; +/// let (results, mut stream) = room_directory_search.results(); +/// room_directory_search.next_page().await?; +/// ... +/// # } +/// ``` #[derive(Debug)] pub struct RoomDirectorySearch { batch_size: u32, @@ -75,6 +92,7 @@ pub struct RoomDirectorySearch { } impl RoomDirectorySearch { + /// Constructor for the `RoomDirectorySearch`, requires a `Client`. pub fn new(client: Client) -> Self { Self { batch_size: 0, @@ -326,6 +344,12 @@ mod tests { room_directory_search.search(None, 1).await.unwrap(); + let (results, mut stream) = room_directory_search.results(); + assert_eq!(results, vec![get_first_page_description()].into()); + assert!(!room_directory_search.is_at_last_page()); + assert_eq!(room_directory_search.loaded_pages(), 1); + assert_pending!(stream); + Mock::given(RoomDirectorySearchMatcher { next_token: Some("p190q".into()), filter_term: None, @@ -336,11 +360,10 @@ mod tests { .await; assert!(room_directory_search.next_page().await.is_err()); - - let (results, _) = room_directory_search.results(); assert_eq!(results, vec![get_first_page_description()].into()); assert!(!room_directory_search.is_at_last_page()); assert_eq!(room_directory_search.loaded_pages(), 1); + assert_pending!(stream); } #[async_test] From 8890bf3cee182bf6961499caebf601bb4055fb50 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Thu, 29 Feb 2024 14:10:45 +0100 Subject: [PATCH 16/30] feat(bindings): improved and fixed ffi code --- .../src/room_directory_search.rs | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/room_directory_search.rs b/bindings/matrix-sdk-ffi/src/room_directory_search.rs index ea549b53090..c566a9001ad 100644 --- a/bindings/matrix-sdk-ffi/src/room_directory_search.rs +++ b/bindings/matrix-sdk-ffi/src/room_directory_search.rs @@ -16,8 +16,7 @@ use std::{fmt::Debug, sync::Arc}; use eyeball_im::VectorDiff; -use futures::StreamExt; -use futures_util::pin_mut; +use futures_util::StreamExt; use matrix_sdk::room_directory_search::RoomDirectorySearch as SdkRoomDirectorySearch; use tokio::sync::RwLock; @@ -92,29 +91,20 @@ impl RoomDirectorySearch { Ok(()) } - pub async fn loaded_pages(&self) -> Result { - let inner = self.inner.read().await; - Ok(inner.loaded_pages() as u32) - } - - pub async fn is_at_last_page(&self) -> Result { - let inner = self.inner.read().await; - Ok(inner.is_at_last_page()) - } - - pub async fn results( - &self, - listener: Box, - ) -> TaskHandle { - let (initial_values, stream) = self.inner.read().await.results(); + pub async fn state(&self, listener: Box) -> TaskHandle { + let (initial_values, mut stream) = self.inner.read().await.results(); TaskHandle::new(RUNTIME.spawn(async move { - listener.on_update(RoomDirectorySearchEntryUpdate::Reset { - values: initial_values.into_iter().map(Into::into).collect(), + listener.on_update(RoomDirectorySearchState { + updates: vec![RoomDirectorySearchEntryUpdate::Reset { + values: initial_values.into_iter().map(Into::into).collect(), + }], + is_at_last_page: self.inner.read().await.is_at_last_page(), + loaded_pages: self.inner.read().await.loaded_pages() as u32, }); while let Some(diffs) = stream.next().await { - listener.on_update(diffs.into_iter().map(|diff| diff.map(Into::into)).collect()); + listener.on_update(diffs.into_iter().map(|diff| diff.into()).collect()); } })) } @@ -140,25 +130,42 @@ pub enum RoomDirectorySearchEntryUpdate { Reset { values: Vec }, } -impl From> for RoomDirectorySearchEntryUpdate { - fn from(diff: VectorDiff) -> Self { +impl From> + for RoomDirectorySearchEntryUpdate +{ + fn from(diff: VectorDiff) -> Self { match diff { - VectorDiff::Append { values } => Self::Append { values }, + VectorDiff::Append { values } => { + Self::Append { values: values.into_iter().map(|v| v.into()).collect() } + } VectorDiff::Clear => Self::Clear, - VectorDiff::PushFront { value } => Self::PushFront { value }, - VectorDiff::PushBack { value } => Self::PushBack { value }, + 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, value }, - VectorDiff::Set { index, value } => Self::Set { index, value }, - VectorDiff::Remove { index } => Self::Remove { index }, - VectorDiff::Truncate { length } => Self::Truncate { length }, - VectorDiff::Reset { values } => Self::Reset { values }, + 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() } + } } } } +#[derive(uniffi::Record)] +struct RoomDirectorySearchState { + pub updates: Vec, + pub is_at_last_page: bool, + pub loaded_pages: u32, +} + #[uniffi::export(callback_interface)] pub trait RoomDirectorySearchEntriesListener: Send + Sync + Debug { - fn on_update(&self, room_entries_update: RoomDirectorySearchEntryUpdate); + fn on_update(&self, room_entries_update: RoomDirectorySearchState); } From b2b9b5fa120bc24277f7d4530d635216d4c58aee Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Thu, 29 Feb 2024 14:29:03 +0100 Subject: [PATCH 17/30] feat(bindings): reverted some code from ffi --- .../src/room_directory_search.rs | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/room_directory_search.rs b/bindings/matrix-sdk-ffi/src/room_directory_search.rs index c566a9001ad..35a5629ecb6 100644 --- a/bindings/matrix-sdk-ffi/src/room_directory_search.rs +++ b/bindings/matrix-sdk-ffi/src/room_directory_search.rs @@ -91,17 +91,26 @@ impl RoomDirectorySearch { Ok(()) } - pub async fn state(&self, listener: Box) -> TaskHandle { + pub async fn loaded_pages(&self) -> Result { + let inner = self.inner.read().await; + Ok(inner.loaded_pages() as u32) + } + + pub async fn is_at_last_page(&self) -> Result { + let inner = self.inner.read().await; + Ok(inner.is_at_last_page()) + } + + pub async fn results( + &self, + listener: Box, + ) -> TaskHandle { let (initial_values, mut stream) = self.inner.read().await.results(); TaskHandle::new(RUNTIME.spawn(async move { - listener.on_update(RoomDirectorySearchState { - updates: vec![RoomDirectorySearchEntryUpdate::Reset { - values: initial_values.into_iter().map(Into::into).collect(), - }], - is_at_last_page: self.inner.read().await.is_at_last_page(), - loaded_pages: self.inner.read().await.loaded_pages() as u32, - }); + 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()); @@ -158,14 +167,7 @@ impl From> } } -#[derive(uniffi::Record)] -struct RoomDirectorySearchState { - pub updates: Vec, - pub is_at_last_page: bool, - pub loaded_pages: u32, -} - #[uniffi::export(callback_interface)] pub trait RoomDirectorySearchEntriesListener: Send + Sync + Debug { - fn on_update(&self, room_entries_update: RoomDirectorySearchState); + fn on_update(&self, room_entries_update: Vec); } From 2abe3aba4adf937a9267d94901543b327235c6b5 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Fri, 1 Mar 2024 15:21:57 +0100 Subject: [PATCH 18/30] improved docs --- crates/matrix-sdk/src/room_directory_search.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index e81a2a9bfbe..0dca713616f 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -70,15 +70,17 @@ impl From for RoomDescription { /// /// # Example /// -/// ```rust -/// use matrix_sdk::room_directory_search::RoomDirectorySearch; +/// ```no_run +/// use matrix_sdk::{room_directory_search::RoomDirectorySearch, Client}; +/// use url::Url; /// /// # fn main() -> Result<()> { +/// let homeserver = Url::parse("http://localhost:8080")?; +/// let client = Client::new(homeserver).await?; /// let room_directory_search = RoomDirectorySearch(client); /// room_directory_search.search(None, 10).await?; /// let (results, mut stream) = room_directory_search.results(); /// room_directory_search.next_page().await?; -/// ... /// # } /// ``` #[derive(Debug)] From ad1623da58e156a432b055985cb1747f30912c03 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Fri, 1 Mar 2024 15:44:24 +0100 Subject: [PATCH 19/30] docs: fixed docs --- crates/matrix-sdk/src/room_directory_search.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 0dca713616f..9637b7bdd48 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -74,14 +74,15 @@ impl From for RoomDescription { /// use matrix_sdk::{room_directory_search::RoomDirectorySearch, Client}; /// use url::Url; /// -/// # fn main() -> Result<()> { -/// let homeserver = Url::parse("http://localhost:8080")?; -/// let client = Client::new(homeserver).await?; -/// let room_directory_search = RoomDirectorySearch(client); -/// room_directory_search.search(None, 10).await?; -/// let (results, mut stream) = room_directory_search.results(); -/// room_directory_search.next_page().await?; -/// # } +/// async { +/// let homeserver = Url::parse("http://localhost:8080")?; +/// let client = Client::new(homeserver).await?; +/// let mut room_directory_search = RoomDirectorySearch::new(client); +/// room_directory_search.search(None, 10).await?; +/// let (results, mut stream) = room_directory_search.results(); +/// room_directory_search.next_page().await?; +/// anyhow::Ok(()) +/// }; /// ``` #[derive(Debug)] pub struct RoomDirectorySearch { From 2f7b2f0451b31e664168312247e15f03e0e7b673 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Fri, 1 Mar 2024 17:07:52 +0100 Subject: [PATCH 20/30] fix: fmt --- .../src/tests/room_directory_search.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs index 3348bd14303..fb58da2eedd 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs @@ -47,7 +47,8 @@ async fn test_room_directory_search_filter() -> Result<()> { let (values, mut stream) = room_directory_search.results(); assert!(values.is_empty()); room_directory_search.search(Some(search_string), 10).await?; - let results_batch: Vec> = stream.next().await.unwrap(); + let results_batch: Vec> = + stream.next().await.unwrap(); assert_matches!(&results_batch[0], VectorDiff::Clear); assert_matches!(&results_batch[1], VectorDiff::Append { values } => { assert_eq!(values.len(), 10); }); From 9fbc2ab07c76ee499c90054315c7c5155141b87a Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Fri, 1 Mar 2024 17:17:24 +0100 Subject: [PATCH 21/30] mocking library not supported on wasm --- crates/matrix-sdk/src/room_directory_search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 9637b7bdd48..3eec4bc44ef 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -171,7 +171,7 @@ impl RoomDirectorySearch { } } -#[cfg(test)] +#[cfg(all(test, not(target_arch = "wasm32")))] mod tests { use assert_matches::assert_matches; use eyeball_im::VectorDiff; From e922a58cc3f40fb2fa87b67a9370e754974ac736 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Fri, 1 Mar 2024 17:25:11 +0100 Subject: [PATCH 22/30] tests: fix fmt --- .../src/tests/room_directory_search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs index fb58da2eedd..ff3c3d35ba6 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/room_directory_search.rs @@ -24,7 +24,7 @@ use matrix_sdk::{ ruma::api::client::room::{create_room::v3::Request as CreateRoomRequest, Visibility}, }; use rand::{thread_rng, Rng}; -use stream_assert::{assert_next_eq, assert_pending}; +use stream_assert::assert_pending; use tokio::time::sleep; use tracing::warn; From a52a2329a1a47d8cf631410d28fb438f31841fdc Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Wed, 13 Mar 2024 12:43:26 +0100 Subject: [PATCH 23/30] pr suggestions improved the conversion by using a try from and changed the nex_token to a search state to indicate the current state of the search --- .../src/room_directory_search.rs | 14 +++-- .../matrix-sdk/src/room_directory_search.rs | 59 +++++++++++++------ 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/room_directory_search.rs b/bindings/matrix-sdk-ffi/src/room_directory_search.rs index 35a5629ecb6..3218b3e2fcc 100644 --- a/bindings/matrix-sdk-ffi/src/room_directory_search.rs +++ b/bindings/matrix-sdk-ffi/src/room_directory_search.rs @@ -29,12 +29,14 @@ pub enum PublicRoomJoinRule { Knock, } -impl PublicRoomJoinRule { - fn convert(value: ruma::directory::PublicRoomJoinRule) -> Option { +impl TryFrom for PublicRoomJoinRule { + type Error = (); + + fn try_from(value: ruma::directory::PublicRoomJoinRule) -> Result { match value { - ruma::directory::PublicRoomJoinRule::Public => Some(Self::Public), - ruma::directory::PublicRoomJoinRule::Knock => Some(Self::Knock), - _ => None, + ruma::directory::PublicRoomJoinRule::Public => Ok(Self::Public), + ruma::directory::PublicRoomJoinRule::Knock => Ok(Self::Knock), + _ => Err(()), } } } @@ -59,7 +61,7 @@ impl From for RoomDescriptio topic: value.topic, alias: value.alias.map(|alias| alias.to_string()), avatar_url: value.avatar_url.map(|url| url.to_string()), - join_rule: PublicRoomJoinRule::convert(value.join_rule), + join_rule: value.join_rule.try_into().ok(), is_world_readable: value.is_world_readable, joined_members: value.joined_members, } diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 3eec4bc44ef..96dd7274f62 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -63,6 +63,34 @@ impl From for RoomDescription { } } } + +#[derive(Default, Debug)] +enum SearchState { + /// The search has more pages and contains the next token to be used in the + /// next page request. + Next(String), + /// The search has reached the end. + End, + /// The search is in a starting state, and has yet to fetch the first page. + #[default] + Start, +} + +// if you want extra methods: +impl SearchState { + fn next_token(&self) -> Option<&str> { + if let Self::Next(next_token) = &self { + Some(next_token) + } else { + None + } + } + + fn is_at_end(&self) -> bool { + matches!(self, Self::End) + } +} + /// RoomDirectorySearch allows searching the public room directory, with the /// capability of using a filter and a batch_size. This struct is also /// responsible for keeping the current state of the search, and exposing an @@ -88,10 +116,9 @@ impl From for RoomDescription { pub struct RoomDirectorySearch { batch_size: u32, filter: Option, - next_token: Option, + search_state: SearchState, client: Client, results: ObservableVector, - is_at_last_page: bool, } impl RoomDirectorySearch { @@ -100,10 +127,9 @@ impl RoomDirectorySearch { Self { batch_size: 0, filter: None, - next_token: None, + search_state: Default::default(), client, results: ObservableVector::new(), - is_at_last_page: false, } } @@ -114,23 +140,21 @@ impl RoomDirectorySearch { /// per request. /// /// This method will clear the current search results and start a new one. - /// Should never be used concurrently with another `next_page` or a - /// `search`. + // Should never be used concurrently with another `next_page` or a + // `search`. pub async fn search(&mut self, filter: Option, batch_size: u32) -> Result<()> { self.filter = filter; self.batch_size = batch_size; - self.next_token = None; + self.search_state = Default::default(); self.results.clear(); - self.is_at_last_page = false; self.next_page().await } /// Asks the server for the next page of the current search. - /// - /// Should never be used concurrently with another `next_page` or a - /// `search`. + // Should never be used concurrently with another `next_page` or a + // `search`. pub async fn next_page(&mut self) -> Result<()> { - if self.is_at_last_page { + if self.search_state.is_at_end() { return Ok(()); } let mut filter = Filter::new(); @@ -139,11 +163,12 @@ impl RoomDirectorySearch { let mut request = PublicRoomsFilterRequest::new(); request.filter = filter; request.limit = Some(self.batch_size.into()); - request.since = self.next_token.clone(); + request.since = self.search_state.next_token().map(ToOwned::to_owned); let response = self.client.public_rooms_filtered(request).await?; - self.next_token = response.next_batch; - if self.next_token.is_none() { - self.is_at_last_page = true; + if let Some(next_token) = response.next_batch { + self.search_state = SearchState::Next(next_token); + } else { + self.search_state = SearchState::End; } self.results.append(response.chunk.into_iter().map(Into::into).collect()); Ok(()) @@ -167,7 +192,7 @@ impl RoomDirectorySearch { /// Get whether the search is at the last page pub fn is_at_last_page(&self) -> bool { - self.is_at_last_page + self.search_state.is_at_end() } } From 2f9b9942c30a452a3a59db2d4c9ec1982ab71699 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Wed, 13 Mar 2024 12:45:50 +0100 Subject: [PATCH 24/30] docs: removed useless comment --- crates/matrix-sdk/src/room_directory_search.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 96dd7274f62..3f5febe969c 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -76,7 +76,6 @@ enum SearchState { Start, } -// if you want extra methods: impl SearchState { fn next_token(&self) -> Option<&str> { if let Self::Next(next_token) = &self { From 73b01743a58d526190de2c8cb7add3bd0f2fcc5a Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Wed, 13 Mar 2024 12:48:32 +0100 Subject: [PATCH 25/30] improved code spacing --- crates/matrix-sdk/src/room_directory_search.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 3f5febe969c..24586d3512e 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -156,6 +156,7 @@ impl RoomDirectorySearch { if self.search_state.is_at_end() { return Ok(()); } + let mut filter = Filter::new(); filter.generic_search_term = self.filter.clone(); @@ -163,12 +164,15 @@ impl RoomDirectorySearch { request.filter = filter; request.limit = Some(self.batch_size.into()); request.since = self.search_state.next_token().map(ToOwned::to_owned); + let response = self.client.public_rooms_filtered(request).await?; + if let Some(next_token) = response.next_batch { self.search_state = SearchState::Next(next_token); } else { self.search_state = SearchState::End; } + self.results.append(response.chunk.into_iter().map(Into::into).collect()); Ok(()) } From 5e692931dd151c532eacb54b975a3b0a52f31cb5 Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Thu, 14 Mar 2024 10:42:27 +0100 Subject: [PATCH 26/30] Apply suggestions from code review Co-authored-by: Ivan Enderlin Signed-off-by: Mauro <34335419+Velin92@users.noreply.github.com> --- bindings/matrix-sdk-ffi/src/room_directory_search.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/room_directory_search.rs b/bindings/matrix-sdk-ffi/src/room_directory_search.rs index 3218b3e2fcc..7036d7f5bf0 100644 --- a/bindings/matrix-sdk-ffi/src/room_directory_search.rs +++ b/bindings/matrix-sdk-ffi/src/room_directory_search.rs @@ -30,13 +30,13 @@ pub enum PublicRoomJoinRule { } impl TryFrom for PublicRoomJoinRule { - type Error = (); + type Error = String; fn try_from(value: ruma::directory::PublicRoomJoinRule) -> Result { match value { ruma::directory::PublicRoomJoinRule::Public => Ok(Self::Public), ruma::directory::PublicRoomJoinRule::Knock => Ok(Self::Knock), - _ => Err(()), + rule => Err(format!("unsupported join rule: {rule:?}")), } } } From 4b85a81347e2e3dec6b9393d2b6087ba7914cf08 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Thu, 14 Mar 2024 16:53:18 +0100 Subject: [PATCH 27/30] docs: warning about NSFW content --- crates/matrix-sdk/src/room_directory_search.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 24586d3512e..55e38d5d188 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -94,6 +94,8 @@ impl SearchState { /// capability of using a filter and a batch_size. This struct is also /// responsible for keeping the current state of the search, and exposing an /// update of stream of the results, reset the search, or ask for the next page. +/// NOTE: Users must take great care when using the public room search since the +/// results might contains NSFW content. /// /// # Example /// From 229105536bc815c7064834333b468b406fc040fc Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Thu, 14 Mar 2024 17:55:54 +0100 Subject: [PATCH 28/30] docs: updated docs --- crates/matrix-sdk/src/room_directory_search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 55e38d5d188..d8a367c0e72 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -94,7 +94,7 @@ impl SearchState { /// capability of using a filter and a batch_size. This struct is also /// responsible for keeping the current state of the search, and exposing an /// update of stream of the results, reset the search, or ask for the next page. -/// NOTE: Users must take great care when using the public room search since the +/// Users must take great care when using the public room search since the /// results might contains NSFW content. /// /// # Example From 0a02a41a14f8e85da9805828c76705976a5cf7d1 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 20 Mar 2024 15:28:03 +0100 Subject: [PATCH 29/30] doc(sdk): Fix mark up. Signed-off-by: Ivan Enderlin --- crates/matrix-sdk/src/room_directory_search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index d8a367c0e72..493a1599522 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -90,7 +90,7 @@ impl SearchState { } } -/// RoomDirectorySearch allows searching the public room directory, with the +/// `RoomDirectorySearch` allows searching the public room directory, with the /// capability of using a filter and a batch_size. This struct is also /// responsible for keeping the current state of the search, and exposing an /// update of stream of the results, reset the search, or ask for the next page. From 96c7b3fc527d6d32e8e134e12a4be4a9d4e5c611 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 20 Mar 2024 15:28:24 +0100 Subject: [PATCH 30/30] doc(sdk): Add a warning. Signed-off-by: Ivan Enderlin --- crates/matrix-sdk/src/room_directory_search.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk/src/room_directory_search.rs b/crates/matrix-sdk/src/room_directory_search.rs index 493a1599522..165ca52eae5 100644 --- a/crates/matrix-sdk/src/room_directory_search.rs +++ b/crates/matrix-sdk/src/room_directory_search.rs @@ -94,7 +94,8 @@ impl SearchState { /// capability of using a filter and a batch_size. This struct is also /// responsible for keeping the current state of the search, and exposing an /// update of stream of the results, reset the search, or ask for the next page. -/// Users must take great care when using the public room search since the +/// +/// ⚠️ Users must take great care when using the public room search since the /// results might contains NSFW content. /// /// # Example