Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(room_preview): add RoomListItem::preview_room #4152

Merged
merged 4 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions bindings/matrix-sdk-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1027,7 +1027,7 @@ impl Client {
&self,
room_id: String,
via_servers: Vec<String>,
) -> Result<RoomPreview, ClientError> {
) -> Result<Arc<RoomPreview>, ClientError> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need all the Arcs here? (I'm not very sure about it myself, but I suspect we don't since RoomPreview is now a uniffi object)

let room_id = RoomId::parse(&room_id).context("room_id is not a valid room id")?;

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

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

Ok(RoomPreview::from_sdk(sdk_room_preview))
Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
}

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

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

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

Ok(RoomPreview::from_sdk(sdk_room_preview))
Ok(Arc::new(RoomPreview::new(self.inner.clone(), room_preview)))
}

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

/// The rule used for users wishing to join this room.
#[derive(uniffi::Enum)]
#[derive(Debug, Clone, uniffi::Enum)]
pub enum JoinRule {
/// Anyone can join the room without any prior action.
Public,
Expand All @@ -1830,10 +1830,16 @@ pub enum JoinRule {
/// conditions described in a set of [`AllowRule`]s, or they can request
/// an invite to the room.
KnockRestricted { rules: Vec<AllowRule> },

/// A custom join rule, up for interpretation by the consumer.
Custom {
/// The string representation for this custom rule.
repr: String,
},
}

/// An allow rule which defines a condition that allows joining a room.
#[derive(uniffi::Enum)]
#[derive(Debug, Clone, uniffi::Enum)]
pub enum AllowRule {
/// Only a member of the `room_id` Room can join the one this rule is used
/// in.
Expand All @@ -1857,6 +1863,7 @@ impl TryFrom<JoinRule> for ruma::events::room::join_rules::JoinRule {
let rules = allow_rules_from(rules)?;
Ok(Self::KnockRestricted(ruma::events::room::join_rules::Restricted::new(rules)))
}
JoinRule::Custom { repr } => Ok(serde_json::from_str(&repr)?),
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions bindings/matrix-sdk-ffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use matrix_sdk::{
};
use matrix_sdk_ui::{encryption_sync_service, notification_client, sync_service, timeline};
use uniffi::UnexpectedUniFFICallbackError;

use crate::room_list::RoomListError;

#[derive(Debug, thiserror::Error)]
pub enum ClientError {
#[error("client error: {msg}")]
Expand Down Expand Up @@ -128,6 +131,12 @@ impl From<RoomError> for ClientError {
}
}

impl From<RoomListError> for ClientError {
fn from(e: RoomListError) -> Self {
Self::new(e)
}
}

impl From<EventCacheError> for ClientError {
fn from(e: EventCacheError) -> Self {
Self::new(e)
Expand Down
2 changes: 1 addition & 1 deletion bindings/matrix-sdk-ffi/src/room.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ use crate::{
TaskHandle,
};

#[derive(Debug, uniffi::Enum)]
#[derive(Debug, Clone, uniffi::Enum)]
pub enum Membership {
Invited,
Joined,
Expand Down
63 changes: 55 additions & 8 deletions bindings/matrix-sdk-ffi/src/room_list.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
use std::{fmt::Debug, mem::MaybeUninit, ptr::addr_of_mut, sync::Arc, time::Duration};
#![allow(deprecated)]

use std::{
fmt::Debug,
mem::{ManuallyDrop, MaybeUninit},
ptr::addr_of_mut,
sync::Arc,
time::Duration,
};

use eyeball_im::VectorDiff;
use futures_util::{pin_mut, StreamExt, TryFutureExt};
Expand All @@ -16,12 +24,14 @@ use matrix_sdk_ui::{
timeline::default_event_filter,
unable_to_decrypt_hook::UtdHookManager,
};
use ruma::{OwnedRoomOrAliasId, OwnedServerName, ServerName};
use tokio::sync::RwLock;

use crate::{
error::ClientError,
room::{Membership, Room},
room_info::RoomInfo,
room_preview::RoomPreview,
timeline::{EventTimelineItem, Timeline},
timeline_event_filter::TimelineEventTypeFilter,
TaskHandle, RUNTIME,
Expand All @@ -48,7 +58,7 @@ pub enum RoomListError {
#[error("Event cache ran into an error: {error}")]
EventCache { error: String },
#[error("The requested room doesn't match the membership requirements {expected:?}, observed {actual:?}")]
IncorrectRoomMembership { expected: Membership, actual: Membership },
IncorrectRoomMembership { expected: Vec<Membership>, actual: Membership },
}

impl From<matrix_sdk_ui::room_list_service::Error> for RoomListError {
Expand Down Expand Up @@ -574,31 +584,68 @@ impl RoomListItem {
}

/// Builds a `Room` FFI from an invited room without initializing its
/// internal timeline
/// internal timeline.
///
/// An error will be returned if the room is a state different than invited
/// An error will be returned if the room is a state different than invited.
///
/// ⚠️ Holding on to this room instance after it has been joined is not
/// safe. Use `full_room` instead
/// safe. Use `full_room` instead.
#[deprecated(note = "Please use `preview_room` instead.")]
fn invited_room(&self) -> Result<Arc<Room>, RoomListError> {
if !matches!(self.membership(), Membership::Invited) {
return Err(RoomListError::IncorrectRoomMembership {
expected: Membership::Invited,
expected: vec![Membership::Invited],
actual: self.membership(),
});
}

Ok(Arc::new(Room::new(self.inner.inner_room().clone())))
}

/// Builds a `RoomPreview` from a room list item. This is intended for
/// invited or knocked rooms.
///
/// An error will be returned if the room is in a state other than invited
/// or knocked.
async fn preview_room(&self, via: Vec<String>) -> Result<Arc<RoomPreview>, ClientError> {
// Validate parameters first.
let server_names: Vec<OwnedServerName> = via
.into_iter()
.map(|server| ServerName::parse(server).map_err(ClientError::from))
.collect::<Result<_, ClientError>>()?;

// Validate internal room state.
let membership = self.membership();
if !matches!(membership, Membership::Invited | Membership::Knocked) {
return Err(RoomListError::IncorrectRoomMembership {
expected: vec![Membership::Invited, Membership::Knocked],
actual: membership,
}
.into());
}

// Do the thing.
let client = self.inner.client();
let (room_or_alias_id, server_names) = if let Some(alias) = self.inner.canonical_alias() {
let room_or_alias_id: OwnedRoomOrAliasId = alias.into();
(room_or_alias_id, Vec::new())
} else {
let room_or_alias_id: OwnedRoomOrAliasId = self.inner.id().to_owned().into();
(room_or_alias_id, server_names)
};

let room_preview = client.get_room_preview(&room_or_alias_id, server_names).await?;

Ok(Arc::new(RoomPreview::new(ManuallyDrop::new(client), room_preview)))
}

/// Build a full `Room` FFI object, filling its associated timeline.
///
/// An error will be returned if the room is a state different than joined
/// or if its internal timeline hasn't been initialized.
fn full_room(&self) -> Result<Arc<Room>, RoomListError> {
if !matches!(self.membership(), Membership::Joined) {
return Err(RoomListError::IncorrectRoomMembership {
expected: Membership::Joined,
expected: vec![Membership::Joined],
actual: self.membership(),
});
}
Expand Down
121 changes: 92 additions & 29 deletions bindings/matrix-sdk-ffi/src/room_preview.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,78 @@
use matrix_sdk::{room_preview::RoomPreview as SdkRoomPreview, RoomState};
use std::mem::ManuallyDrop;

use anyhow::Context as _;
use async_compat::TOKIO1 as RUNTIME;
use matrix_sdk::{room_preview::RoomPreview as SdkRoomPreview, Client};
use ruma::space::SpaceRoomJoinRule;
use tracing::warn;

use crate::{client::JoinRule, error::ClientError, room::Membership};

/// A room preview for a room. It's intended to be used to represent rooms that
/// aren't joined yet.
#[derive(uniffi::Object)]
pub struct RoomPreview {
inner: SdkRoomPreview,
client: ManuallyDrop<Client>,
}

impl Drop for RoomPreview {
fn drop(&mut self) {
// Dropping the inner OlmMachine must happen within a tokio context
// because deadpool drops sqlite connections in the DB pool on tokio's
// blocking threadpool to avoid blocking async worker threads.
let _guard = RUNTIME.enter();
// SAFETY: self.client is never used again, which is the only requirement
// for ManuallyDrop::drop to be used safely.
unsafe {
ManuallyDrop::drop(&mut self.client);
}
}
}

#[matrix_sdk_ffi_macros::export]
impl RoomPreview {
/// Returns the room info the preview contains.
pub fn info(&self) -> Result<RoomPreviewInfo, ClientError> {
let info = &self.inner;
Ok(RoomPreviewInfo {
room_id: info.room_id.to_string(),
canonical_alias: info.canonical_alias.as_ref().map(|alias| alias.to_string()),
name: info.name.clone(),
topic: info.topic.clone(),
avatar_url: info.avatar_url.as_ref().map(|url| url.to_string()),
num_joined_members: info.num_joined_members,
room_type: info.room_type.as_ref().map(|room_type| room_type.to_string()),
is_history_world_readable: info.is_world_readable,
membership: info.state.map(|state| state.into()),
join_rule: info
.join_rule
.clone()
.try_into()
.map_err(|_| anyhow::anyhow!("unhandled SpaceRoomJoinRule kind"))?,
})
}

/// Leave the room if the room preview state is either joined, invited or
/// knocked.
///
/// Will return an error otherwise.
pub async fn leave(&self) -> Result<(), ClientError> {
let room =
self.client.get_room(&self.inner.room_id).context("missing room for a room preview")?;
room.leave().await.map_err(Into::into)
}
}

impl RoomPreview {
pub(crate) fn new(client: ManuallyDrop<Client>, inner: SdkRoomPreview) -> Self {
Self { client, inner }
}
}

/// The preview of a room, be it invited/joined/left, or not.
#[derive(uniffi::Record)]
pub struct RoomPreview {
pub struct RoomPreviewInfo {
/// The room id for this room.
pub room_id: String,
/// The canonical alias for the room.
Expand All @@ -20,34 +89,28 @@ pub struct RoomPreview {
pub room_type: Option<String>,
/// Is the history world-readable for this room?
pub is_history_world_readable: bool,
/// Is the room joined by the current user?
pub is_joined: bool,
/// Is the current user invited to this room?
pub is_invited: bool,
/// is the join rule public for this room?
pub is_public: bool,
/// Can we knock (or restricted-knock) to this room?
pub can_knock: bool,
/// The membership state for the current user, if known.
pub membership: Option<Membership>,
/// The join rule for this room (private, public, knock, etc.).
pub join_rule: JoinRule,
}

impl RoomPreview {
pub(crate) fn from_sdk(preview: SdkRoomPreview) -> Self {
Self {
room_id: preview.room_id.to_string(),
canonical_alias: preview.canonical_alias.map(|alias| alias.to_string()),
name: preview.name,
topic: preview.topic,
avatar_url: preview.avatar_url.map(|url| url.to_string()),
num_joined_members: preview.num_joined_members,
room_type: preview.room_type.map(|room_type| room_type.to_string()),
is_history_world_readable: preview.is_world_readable,
is_joined: preview.state.map_or(false, |state| state == RoomState::Joined),
is_invited: preview.state.map_or(false, |state| state == RoomState::Invited),
is_public: preview.join_rule == SpaceRoomJoinRule::Public,
can_knock: matches!(
preview.join_rule,
SpaceRoomJoinRule::KnockRestricted | SpaceRoomJoinRule::Knock
),
}
impl TryFrom<SpaceRoomJoinRule> for JoinRule {
type Error = ();

fn try_from(join_rule: SpaceRoomJoinRule) -> Result<Self, ()> {
Ok(match join_rule {
SpaceRoomJoinRule::Invite => JoinRule::Invite,
SpaceRoomJoinRule::Knock => JoinRule::Knock,
SpaceRoomJoinRule::Private => JoinRule::Private,
SpaceRoomJoinRule::Restricted => JoinRule::Restricted { rules: Vec::new() },
SpaceRoomJoinRule::KnockRestricted => JoinRule::KnockRestricted { rules: Vec::new() },
SpaceRoomJoinRule::Public => JoinRule::Public,
SpaceRoomJoinRule::_Custom(_) => JoinRule::Custom { repr: join_rule.to_string() },
_ => {
warn!("unhandled SpaceRoomJoinRule: {join_rule}");
return Err(());
}
})
}
}
6 changes: 3 additions & 3 deletions crates/matrix-sdk-base/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::{collections::BTreeMap, fmt};
use matrix_sdk_common::{debug::DebugRawEvent, deserialized_responses::SyncTimelineEvent};
use ruma::{
api::client::sync::sync_events::{
v3::{InvitedRoom as InvitedRoomUpdate, KnockedRoom},
v3::{InvitedRoom as InvitedRoomUpdate, KnockedRoom as KnockedRoomUpdate},
UnreadNotificationsCount as RumaUnreadNotificationsCount,
},
events::{
Expand Down Expand Up @@ -78,7 +78,7 @@ pub struct RoomUpdates {
/// The rooms that the user has been invited to.
pub invite: BTreeMap<OwnedRoomId, InvitedRoomUpdate>,
/// The rooms that the user has knocked on.
pub knocked: BTreeMap<OwnedRoomId, KnockedRoom>,
pub knocked: BTreeMap<OwnedRoomId, KnockedRoomUpdate>,
}

impl RoomUpdates {
Expand Down Expand Up @@ -254,7 +254,7 @@ impl<'a> fmt::Debug for DebugInvitedRoomUpdates<'a> {
}
}

struct DebugKnockedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, KnockedRoom>);
struct DebugKnockedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, KnockedRoomUpdate>);

#[cfg(not(tarpaulin_include))]
impl<'a> fmt::Debug for DebugKnockedRoomUpdates<'a> {
Expand Down
2 changes: 1 addition & 1 deletion crates/matrix-sdk/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,7 @@ impl Client {
};

if let Some(room) = self.get_room(&room_id) {
return Ok(RoomPreview::from_known(&room));
return Ok(RoomPreview::from_known(&room).await);
}

RoomPreview::from_unknown(self, room_id, room_or_alias_id, via).await
Expand Down
Loading
Loading