Skip to content

Commit 40f4fc1

Browse files
jmartinespbnjbvr
andauthored
chore(room_preview): add RoomListItem::preview_room (#4152)
This method will return a `RoomPreview` for the provided room id. Also added `fn RoomPreview::leave()` action to be able to decline invites or cancel knocks, since there wasn't a `Client::leave_room_by_id` counterpart as there is for join. The PR also deprecates `RoomListItem::invited_room`, since we have a better alternative now. Co-authored-by: Benjamin Bouvier <[email protected]>
1 parent d3d7c03 commit 40f4fc1

File tree

14 files changed

+411
-64
lines changed

14 files changed

+411
-64
lines changed

bindings/matrix-sdk-ffi/src/client.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,7 +1027,7 @@ impl Client {
10271027
&self,
10281028
room_id: String,
10291029
via_servers: Vec<String>,
1030-
) -> Result<RoomPreview, ClientError> {
1030+
) -> Result<Arc<RoomPreview>, ClientError> {
10311031
let room_id = RoomId::parse(&room_id).context("room_id is not a valid room id")?;
10321032

10331033
let via_servers = via_servers
@@ -1040,26 +1040,26 @@ impl Client {
10401040
// rustc win that one fight.
10411041
let room_id: &RoomId = &room_id;
10421042

1043-
let sdk_room_preview = self.inner.get_room_preview(room_id.into(), via_servers).await?;
1043+
let room_preview = self.inner.get_room_preview(room_id.into(), via_servers).await?;
10441044

1045-
Ok(RoomPreview::from_sdk(sdk_room_preview))
1045+
Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
10461046
}
10471047

10481048
/// Given a room alias, get the preview of a room, to interact with it.
10491049
pub async fn get_room_preview_from_room_alias(
10501050
&self,
10511051
room_alias: String,
1052-
) -> Result<RoomPreview, ClientError> {
1052+
) -> Result<Arc<RoomPreview>, ClientError> {
10531053
let room_alias =
10541054
RoomAliasId::parse(&room_alias).context("room_alias is not a valid room alias")?;
10551055

10561056
// The `into()` call below doesn't work if I do `(&room_id).into()`, so I let
10571057
// rustc win that one fight.
10581058
let room_alias: &RoomAliasId = &room_alias;
10591059

1060-
let sdk_room_preview = self.inner.get_room_preview(room_alias.into(), Vec::new()).await?;
1060+
let room_preview = self.inner.get_room_preview(room_alias.into(), Vec::new()).await?;
10611061

1062-
Ok(RoomPreview::from_sdk(sdk_room_preview))
1062+
Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
10631063
}
10641064

10651065
/// Waits until an at least partially synced room is received, and returns
@@ -1804,7 +1804,7 @@ impl From<OidcPrompt> for SdkOidcPrompt {
18041804
}
18051805

18061806
/// The rule used for users wishing to join this room.
1807-
#[derive(uniffi::Enum)]
1807+
#[derive(Debug, Clone, uniffi::Enum)]
18081808
pub enum JoinRule {
18091809
/// Anyone can join the room without any prior action.
18101810
Public,
@@ -1830,10 +1830,16 @@ pub enum JoinRule {
18301830
/// conditions described in a set of [`AllowRule`]s, or they can request
18311831
/// an invite to the room.
18321832
KnockRestricted { rules: Vec<AllowRule> },
1833+
1834+
/// A custom join rule, up for interpretation by the consumer.
1835+
Custom {
1836+
/// The string representation for this custom rule.
1837+
repr: String,
1838+
},
18331839
}
18341840

18351841
/// An allow rule which defines a condition that allows joining a room.
1836-
#[derive(uniffi::Enum)]
1842+
#[derive(Debug, Clone, uniffi::Enum)]
18371843
pub enum AllowRule {
18381844
/// Only a member of the `room_id` Room can join the one this rule is used
18391845
/// in.
@@ -1857,6 +1863,7 @@ impl TryFrom<JoinRule> for ruma::events::room::join_rules::JoinRule {
18571863
let rules = allow_rules_from(rules)?;
18581864
Ok(Self::KnockRestricted(ruma::events::room::join_rules::Restricted::new(rules)))
18591865
}
1866+
JoinRule::Custom { repr } => Ok(serde_json::from_str(&repr)?),
18601867
}
18611868
}
18621869
}

bindings/matrix-sdk-ffi/src/error.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ use matrix_sdk::{
88
};
99
use matrix_sdk_ui::{encryption_sync_service, notification_client, sync_service, timeline};
1010
use uniffi::UnexpectedUniFFICallbackError;
11+
12+
use crate::room_list::RoomListError;
13+
1114
#[derive(Debug, thiserror::Error)]
1215
pub enum ClientError {
1316
#[error("client error: {msg}")]
@@ -128,6 +131,12 @@ impl From<RoomError> for ClientError {
128131
}
129132
}
130133

134+
impl From<RoomListError> for ClientError {
135+
fn from(e: RoomListError) -> Self {
136+
Self::new(e)
137+
}
138+
}
139+
131140
impl From<EventCacheError> for ClientError {
132141
fn from(e: EventCacheError) -> Self {
133142
Self::new(e)

bindings/matrix-sdk-ffi/src/room.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ use crate::{
4545
TaskHandle,
4646
};
4747

48-
#[derive(Debug, uniffi::Enum)]
48+
#[derive(Debug, Clone, uniffi::Enum)]
4949
pub enum Membership {
5050
Invited,
5151
Joined,

bindings/matrix-sdk-ffi/src/room_list.rs

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
use std::{fmt::Debug, mem::MaybeUninit, ptr::addr_of_mut, sync::Arc, time::Duration};
1+
#![allow(deprecated)]
2+
3+
use std::{
4+
fmt::Debug,
5+
mem::{ManuallyDrop, MaybeUninit},
6+
ptr::addr_of_mut,
7+
sync::Arc,
8+
time::Duration,
9+
};
210

311
use eyeball_im::VectorDiff;
412
use futures_util::{pin_mut, StreamExt, TryFutureExt};
@@ -16,12 +24,14 @@ use matrix_sdk_ui::{
1624
timeline::default_event_filter,
1725
unable_to_decrypt_hook::UtdHookManager,
1826
};
27+
use ruma::{OwnedRoomOrAliasId, OwnedServerName, ServerName};
1928
use tokio::sync::RwLock;
2029

2130
use crate::{
2231
error::ClientError,
2332
room::{Membership, Room},
2433
room_info::RoomInfo,
34+
room_preview::RoomPreview,
2535
timeline::{EventTimelineItem, Timeline},
2636
timeline_event_filter::TimelineEventTypeFilter,
2737
TaskHandle, RUNTIME,
@@ -48,7 +58,7 @@ pub enum RoomListError {
4858
#[error("Event cache ran into an error: {error}")]
4959
EventCache { error: String },
5060
#[error("The requested room doesn't match the membership requirements {expected:?}, observed {actual:?}")]
51-
IncorrectRoomMembership { expected: Membership, actual: Membership },
61+
IncorrectRoomMembership { expected: Vec<Membership>, actual: Membership },
5262
}
5363

5464
impl From<matrix_sdk_ui::room_list_service::Error> for RoomListError {
@@ -574,31 +584,68 @@ impl RoomListItem {
574584
}
575585

576586
/// Builds a `Room` FFI from an invited room without initializing its
577-
/// internal timeline
587+
/// internal timeline.
578588
///
579-
/// An error will be returned if the room is a state different than invited
589+
/// An error will be returned if the room is a state different than invited.
580590
///
581591
/// ⚠️ Holding on to this room instance after it has been joined is not
582-
/// safe. Use `full_room` instead
592+
/// safe. Use `full_room` instead.
593+
#[deprecated(note = "Please use `preview_room` instead.")]
583594
fn invited_room(&self) -> Result<Arc<Room>, RoomListError> {
584595
if !matches!(self.membership(), Membership::Invited) {
585596
return Err(RoomListError::IncorrectRoomMembership {
586-
expected: Membership::Invited,
597+
expected: vec![Membership::Invited],
587598
actual: self.membership(),
588599
});
589600
}
590-
591601
Ok(Arc::new(Room::new(self.inner.inner_room().clone())))
592602
}
593603

604+
/// Builds a `RoomPreview` from a room list item. This is intended for
605+
/// invited or knocked rooms.
606+
///
607+
/// An error will be returned if the room is in a state other than invited
608+
/// or knocked.
609+
async fn preview_room(&self, via: Vec<String>) -> Result<Arc<RoomPreview>, ClientError> {
610+
// Validate parameters first.
611+
let server_names: Vec<OwnedServerName> = via
612+
.into_iter()
613+
.map(|server| ServerName::parse(server).map_err(ClientError::from))
614+
.collect::<Result<_, ClientError>>()?;
615+
616+
// Validate internal room state.
617+
let membership = self.membership();
618+
if !matches!(membership, Membership::Invited | Membership::Knocked) {
619+
return Err(RoomListError::IncorrectRoomMembership {
620+
expected: vec![Membership::Invited, Membership::Knocked],
621+
actual: membership,
622+
}
623+
.into());
624+
}
625+
626+
// Do the thing.
627+
let client = self.inner.client();
628+
let (room_or_alias_id, server_names) = if let Some(alias) = self.inner.canonical_alias() {
629+
let room_or_alias_id: OwnedRoomOrAliasId = alias.into();
630+
(room_or_alias_id, Vec::new())
631+
} else {
632+
let room_or_alias_id: OwnedRoomOrAliasId = self.inner.id().to_owned().into();
633+
(room_or_alias_id, server_names)
634+
};
635+
636+
let room_preview = client.get_room_preview(&room_or_alias_id, server_names).await?;
637+
638+
Ok(Arc::new(RoomPreview::new(ManuallyDrop::new(client), room_preview)))
639+
}
640+
594641
/// Build a full `Room` FFI object, filling its associated timeline.
595642
///
596643
/// An error will be returned if the room is a state different than joined
597644
/// or if its internal timeline hasn't been initialized.
598645
fn full_room(&self) -> Result<Arc<Room>, RoomListError> {
599646
if !matches!(self.membership(), Membership::Joined) {
600647
return Err(RoomListError::IncorrectRoomMembership {
601-
expected: Membership::Joined,
648+
expected: vec![Membership::Joined],
602649
actual: self.membership(),
603650
});
604651
}
Lines changed: 92 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,78 @@
1-
use matrix_sdk::{room_preview::RoomPreview as SdkRoomPreview, RoomState};
1+
use std::mem::ManuallyDrop;
2+
3+
use anyhow::Context as _;
4+
use async_compat::TOKIO1 as RUNTIME;
5+
use matrix_sdk::{room_preview::RoomPreview as SdkRoomPreview, Client};
26
use ruma::space::SpaceRoomJoinRule;
7+
use tracing::warn;
8+
9+
use crate::{client::JoinRule, error::ClientError, room::Membership};
10+
11+
/// A room preview for a room. It's intended to be used to represent rooms that
12+
/// aren't joined yet.
13+
#[derive(uniffi::Object)]
14+
pub struct RoomPreview {
15+
inner: SdkRoomPreview,
16+
client: ManuallyDrop<Client>,
17+
}
18+
19+
impl Drop for RoomPreview {
20+
fn drop(&mut self) {
21+
// Dropping the inner OlmMachine must happen within a tokio context
22+
// because deadpool drops sqlite connections in the DB pool on tokio's
23+
// blocking threadpool to avoid blocking async worker threads.
24+
let _guard = RUNTIME.enter();
25+
// SAFETY: self.client is never used again, which is the only requirement
26+
// for ManuallyDrop::drop to be used safely.
27+
unsafe {
28+
ManuallyDrop::drop(&mut self.client);
29+
}
30+
}
31+
}
32+
33+
#[matrix_sdk_ffi_macros::export]
34+
impl RoomPreview {
35+
/// Returns the room info the preview contains.
36+
pub fn info(&self) -> Result<RoomPreviewInfo, ClientError> {
37+
let info = &self.inner;
38+
Ok(RoomPreviewInfo {
39+
room_id: info.room_id.to_string(),
40+
canonical_alias: info.canonical_alias.as_ref().map(|alias| alias.to_string()),
41+
name: info.name.clone(),
42+
topic: info.topic.clone(),
43+
avatar_url: info.avatar_url.as_ref().map(|url| url.to_string()),
44+
num_joined_members: info.num_joined_members,
45+
room_type: info.room_type.as_ref().map(|room_type| room_type.to_string()),
46+
is_history_world_readable: info.is_world_readable,
47+
membership: info.state.map(|state| state.into()),
48+
join_rule: info
49+
.join_rule
50+
.clone()
51+
.try_into()
52+
.map_err(|_| anyhow::anyhow!("unhandled SpaceRoomJoinRule kind"))?,
53+
})
54+
}
55+
56+
/// Leave the room if the room preview state is either joined, invited or
57+
/// knocked.
58+
///
59+
/// Will return an error otherwise.
60+
pub async fn leave(&self) -> Result<(), ClientError> {
61+
let room =
62+
self.client.get_room(&self.inner.room_id).context("missing room for a room preview")?;
63+
room.leave().await.map_err(Into::into)
64+
}
65+
}
66+
67+
impl RoomPreview {
68+
pub(crate) fn new(client: ManuallyDrop<Client>, inner: SdkRoomPreview) -> Self {
69+
Self { client, inner }
70+
}
71+
}
372

473
/// The preview of a room, be it invited/joined/left, or not.
574
#[derive(uniffi::Record)]
6-
pub struct RoomPreview {
75+
pub struct RoomPreviewInfo {
776
/// The room id for this room.
877
pub room_id: String,
978
/// The canonical alias for the room.
@@ -20,34 +89,28 @@ pub struct RoomPreview {
2089
pub room_type: Option<String>,
2190
/// Is the history world-readable for this room?
2291
pub is_history_world_readable: bool,
23-
/// Is the room joined by the current user?
24-
pub is_joined: bool,
25-
/// Is the current user invited to this room?
26-
pub is_invited: bool,
27-
/// is the join rule public for this room?
28-
pub is_public: bool,
29-
/// Can we knock (or restricted-knock) to this room?
30-
pub can_knock: bool,
92+
/// The membership state for the current user, if known.
93+
pub membership: Option<Membership>,
94+
/// The join rule for this room (private, public, knock, etc.).
95+
pub join_rule: JoinRule,
3196
}
3297

33-
impl RoomPreview {
34-
pub(crate) fn from_sdk(preview: SdkRoomPreview) -> Self {
35-
Self {
36-
room_id: preview.room_id.to_string(),
37-
canonical_alias: preview.canonical_alias.map(|alias| alias.to_string()),
38-
name: preview.name,
39-
topic: preview.topic,
40-
avatar_url: preview.avatar_url.map(|url| url.to_string()),
41-
num_joined_members: preview.num_joined_members,
42-
room_type: preview.room_type.map(|room_type| room_type.to_string()),
43-
is_history_world_readable: preview.is_world_readable,
44-
is_joined: preview.state.map_or(false, |state| state == RoomState::Joined),
45-
is_invited: preview.state.map_or(false, |state| state == RoomState::Invited),
46-
is_public: preview.join_rule == SpaceRoomJoinRule::Public,
47-
can_knock: matches!(
48-
preview.join_rule,
49-
SpaceRoomJoinRule::KnockRestricted | SpaceRoomJoinRule::Knock
50-
),
51-
}
98+
impl TryFrom<SpaceRoomJoinRule> for JoinRule {
99+
type Error = ();
100+
101+
fn try_from(join_rule: SpaceRoomJoinRule) -> Result<Self, ()> {
102+
Ok(match join_rule {
103+
SpaceRoomJoinRule::Invite => JoinRule::Invite,
104+
SpaceRoomJoinRule::Knock => JoinRule::Knock,
105+
SpaceRoomJoinRule::Private => JoinRule::Private,
106+
SpaceRoomJoinRule::Restricted => JoinRule::Restricted { rules: Vec::new() },
107+
SpaceRoomJoinRule::KnockRestricted => JoinRule::KnockRestricted { rules: Vec::new() },
108+
SpaceRoomJoinRule::Public => JoinRule::Public,
109+
SpaceRoomJoinRule::_Custom(_) => JoinRule::Custom { repr: join_rule.to_string() },
110+
_ => {
111+
warn!("unhandled SpaceRoomJoinRule: {join_rule}");
112+
return Err(());
113+
}
114+
})
52115
}
53116
}

crates/matrix-sdk-base/src/sync.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use std::{collections::BTreeMap, fmt};
1919
use matrix_sdk_common::{debug::DebugRawEvent, deserialized_responses::SyncTimelineEvent};
2020
use ruma::{
2121
api::client::sync::sync_events::{
22-
v3::{InvitedRoom as InvitedRoomUpdate, KnockedRoom},
22+
v3::{InvitedRoom as InvitedRoomUpdate, KnockedRoom as KnockedRoomUpdate},
2323
UnreadNotificationsCount as RumaUnreadNotificationsCount,
2424
},
2525
events::{
@@ -78,7 +78,7 @@ pub struct RoomUpdates {
7878
/// The rooms that the user has been invited to.
7979
pub invite: BTreeMap<OwnedRoomId, InvitedRoomUpdate>,
8080
/// The rooms that the user has knocked on.
81-
pub knocked: BTreeMap<OwnedRoomId, KnockedRoom>,
81+
pub knocked: BTreeMap<OwnedRoomId, KnockedRoomUpdate>,
8282
}
8383

8484
impl RoomUpdates {
@@ -254,7 +254,7 @@ impl<'a> fmt::Debug for DebugInvitedRoomUpdates<'a> {
254254
}
255255
}
256256

257-
struct DebugKnockedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, KnockedRoom>);
257+
struct DebugKnockedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, KnockedRoomUpdate>);
258258

259259
#[cfg(not(tarpaulin_include))]
260260
impl<'a> fmt::Debug for DebugKnockedRoomUpdates<'a> {

crates/matrix-sdk/src/client/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,7 @@ impl Client {
10091009
};
10101010

10111011
if let Some(room) = self.get_room(&room_id) {
1012-
return Ok(RoomPreview::from_known(&room));
1012+
return Ok(RoomPreview::from_known(&room).await);
10131013
}
10141014

10151015
RoomPreview::from_unknown(self, room_id, room_or_alias_id, via).await

0 commit comments

Comments
 (0)