mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-02-15 01:54:25 -05:00
Merge branch 'main' into mauroromito/directory_search
This commit is contained in:
2
.github/workflows/coverage.yml
vendored
2
.github/workflows/coverage.yml
vendored
@@ -111,7 +111,7 @@ jobs:
|
||||
SLIDING_SYNC_PROXY_URL: "http://localhost:8118"
|
||||
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
# Work around frequent upload errors, for runs inside the main repo (not PRs from forks).
|
||||
# Otherwise not required for public repos.
|
||||
|
||||
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -3442,9 +3442,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.10"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
|
||||
@@ -576,15 +576,21 @@ impl Room {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_power_level_for_user(
|
||||
pub async fn update_power_levels_for_users(
|
||||
&self,
|
||||
user_id: String,
|
||||
power_level: i64,
|
||||
updates: Vec<UserPowerLevelUpdate>,
|
||||
) -> Result<(), ClientError> {
|
||||
let user_id = UserId::parse(&user_id)?;
|
||||
let power_level = Int::new(power_level).context("Invalid power level")?;
|
||||
let updates = updates
|
||||
.iter()
|
||||
.map(|update| {
|
||||
let user_id: &UserId = update.user_id.as_str().try_into()?;
|
||||
let power_level = Int::new(update.power_level).context("Invalid power level")?;
|
||||
Ok((user_id, power_level))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
self.inner
|
||||
.update_power_levels(vec![(&user_id, power_level)])
|
||||
.update_power_levels(updates)
|
||||
.await
|
||||
.map_err(|e| ClientError::Generic { msg: e.to_string() })?;
|
||||
Ok(())
|
||||
@@ -633,6 +639,15 @@ impl RoomMembersIterator {
|
||||
}
|
||||
}
|
||||
|
||||
/// An update for a particular user's power level within the room.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct UserPowerLevelUpdate {
|
||||
/// The user ID of the user to update.
|
||||
user_id: String,
|
||||
/// The power level to assign to the user.
|
||||
power_level: i64,
|
||||
}
|
||||
|
||||
impl TryFrom<ImageInfo> for RumaAvatarImageInfo {
|
||||
type Error = MediaInfoError;
|
||||
|
||||
|
||||
@@ -54,10 +54,10 @@ impl RoomInfo {
|
||||
) -> matrix_sdk::Result<Self> {
|
||||
let unread_notification_counts = room.unread_notification_counts();
|
||||
|
||||
let power_levels = room.room_power_levels().await?;
|
||||
let power_levels_map = room.users_with_power_levels().await;
|
||||
let mut user_power_levels = HashMap::<String, i64>::new();
|
||||
for (id, level) in power_levels.users.iter() {
|
||||
user_power_levels.insert(id.to_string(), (*level).into());
|
||||
for (id, level) in power_levels_map.iter() {
|
||||
user_power_levels.insert(id.to_string(), *level);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
|
||||
@@ -41,6 +41,7 @@ impl TimelineItemContent {
|
||||
}
|
||||
}
|
||||
Content::Poll(poll_state) => TimelineItemContentKind::from(poll_state.results()),
|
||||
Content::CallInvite => TimelineItemContentKind::CallInvite,
|
||||
Content::UnableToDecrypt(msg) => {
|
||||
TimelineItemContentKind::UnableToDecrypt { msg: EncryptedMessage::new(msg) }
|
||||
}
|
||||
@@ -113,6 +114,7 @@ pub enum TimelineItemContentKind {
|
||||
end_time: Option<u64>,
|
||||
has_been_edited: bool,
|
||||
},
|
||||
CallInvite,
|
||||
UnableToDecrypt {
|
||||
msg: EncryptedMessage,
|
||||
},
|
||||
|
||||
@@ -720,11 +720,14 @@ impl BaseClient {
|
||||
// We found an event we can decrypt
|
||||
if let Ok(any_sync_event) = decrypted.event.deserialize() {
|
||||
// We can deserialize it to find its type
|
||||
if let PossibleLatestEvent::YesRoomMessage(_) =
|
||||
is_suitable_for_latest_event(&any_sync_event)
|
||||
{
|
||||
// The event is the right type for us to use as latest_event
|
||||
return Some((Box::new(LatestEvent::new(decrypted)), i));
|
||||
match is_suitable_for_latest_event(&any_sync_event) {
|
||||
PossibleLatestEvent::YesRoomMessage(_)
|
||||
| PossibleLatestEvent::YesPoll(_)
|
||||
| PossibleLatestEvent::YesCallInvite(_) => {
|
||||
// The event is the right type for us to use as latest_event
|
||||
return Some((Box::new(LatestEvent::new(decrypted)), i));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ use ruma::events::{
|
||||
poll::unstable_start::SyncUnstablePollStartEvent, room::message::SyncRoomMessageEvent,
|
||||
AnySyncMessageLikeEvent, AnySyncTimelineEvent,
|
||||
};
|
||||
use ruma::{events::relation::RelationType, MxcUri, OwnedEventId};
|
||||
use ruma::{
|
||||
events::{call::invite::SyncCallInviteEvent, relation::RelationType},
|
||||
MxcUri, OwnedEventId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::MinimalRoomMemberEvent;
|
||||
@@ -25,6 +28,10 @@ pub enum PossibleLatestEvent<'a> {
|
||||
YesRoomMessage(&'a SyncRoomMessageEvent),
|
||||
/// This message is suitable - it is a poll
|
||||
YesPoll(&'a SyncUnstablePollStartEvent),
|
||||
|
||||
/// This message is suitable - it is a call invite
|
||||
YesCallInvite(&'a SyncCallInviteEvent),
|
||||
|
||||
// Later: YesState(),
|
||||
// Later: YesReaction(),
|
||||
/// Not suitable - it's a state event
|
||||
@@ -67,6 +74,10 @@ pub fn is_suitable_for_latest_event(event: &AnySyncTimelineEvent) -> PossibleLat
|
||||
PossibleLatestEvent::YesPoll(poll)
|
||||
}
|
||||
|
||||
AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::CallInvite(invite)) => {
|
||||
PossibleLatestEvent::YesCallInvite(invite)
|
||||
}
|
||||
|
||||
// Encrypted events are not suitable
|
||||
AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(_)) => {
|
||||
PossibleLatestEvent::NoEncrypted
|
||||
@@ -243,6 +254,10 @@ mod tests {
|
||||
use matrix_sdk_common::deserialized_responses::SyncTimelineEvent;
|
||||
use ruma::{
|
||||
events::{
|
||||
call::{
|
||||
invite::{CallInviteEventContent, SyncCallInviteEvent},
|
||||
SessionDescription,
|
||||
},
|
||||
poll::unstable_start::{
|
||||
NewUnstablePollStartEventContent, SyncUnstablePollStartEvent, UnstablePollAnswer,
|
||||
UnstablePollStartContentBlock,
|
||||
@@ -268,7 +283,7 @@ mod tests {
|
||||
},
|
||||
owned_event_id, owned_mxc_uri, owned_user_id,
|
||||
serde::Raw,
|
||||
MilliSecondsSinceUnixEpoch, UInt,
|
||||
MilliSecondsSinceUnixEpoch, UInt, VoipVersionId,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -321,6 +336,28 @@ mod tests {
|
||||
assert_eq!(m.content.poll_start().question.text, "do you like rust?");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_invites_are_suitable() {
|
||||
let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::CallInvite(
|
||||
SyncCallInviteEvent::Original(OriginalSyncMessageLikeEvent {
|
||||
content: CallInviteEventContent::new(
|
||||
"call_id".into(),
|
||||
UInt::new(123).unwrap(),
|
||||
SessionDescription::new("".into(), "".into()),
|
||||
VoipVersionId::V1,
|
||||
),
|
||||
event_id: owned_event_id!("$1"),
|
||||
sender: owned_user_id!("@a:b.c"),
|
||||
origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
|
||||
unsigned: MessageLikeUnsigned::new(),
|
||||
}),
|
||||
));
|
||||
assert_let!(
|
||||
PossibleLatestEvent::YesCallInvite(SyncMessageLikeEvent::Original(_)) =
|
||||
is_suitable_for_latest_event(&event)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_types_of_messagelike_are_unsuitable() {
|
||||
let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::Sticker(
|
||||
|
||||
@@ -585,8 +585,10 @@ async fn cache_latest_events(
|
||||
for event in events.iter().rev() {
|
||||
if let Ok(timeline_event) = event.event.deserialize() {
|
||||
match is_suitable_for_latest_event(&timeline_event) {
|
||||
PossibleLatestEvent::YesRoomMessage(_) | PossibleLatestEvent::YesPoll(_) => {
|
||||
// m.room.message or m.poll.start - we found one! Store it.
|
||||
PossibleLatestEvent::YesRoomMessage(_)
|
||||
| PossibleLatestEvent::YesPoll(_)
|
||||
| PossibleLatestEvent::YesCallInvite(_) => {
|
||||
// We found a suitable latest event. Store it.
|
||||
|
||||
// In order to make the latest event fast to read, we want to keep the
|
||||
// associated sender in cache. This is a best-effort to gather enough
|
||||
|
||||
@@ -127,7 +127,7 @@ impl TimelineBuilder {
|
||||
// Subscribe the event cache to sync responses, in case we hadn't done it yet.
|
||||
event_cache.subscribe()?;
|
||||
|
||||
let (room_event_cache, event_cache_drop) = event_cache.for_room(room.room_id()).await?;
|
||||
let (room_event_cache, event_cache_drop) = room.event_cache().await?;
|
||||
let (events, mut event_subscriber) = room_event_cache.subscribe().await?;
|
||||
|
||||
let has_events = !events.is_empty();
|
||||
|
||||
@@ -309,6 +309,10 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
|
||||
) => self.handle_poll_start(c, should_add),
|
||||
AnyMessageLikeEventContent::UnstablePollResponse(c) => self.handle_poll_response(c),
|
||||
AnyMessageLikeEventContent::UnstablePollEnd(c) => self.handle_poll_end(c),
|
||||
AnyMessageLikeEventContent::CallInvite(_) => {
|
||||
self.add(should_add, TimelineItemContent::CallInvite);
|
||||
}
|
||||
|
||||
// TODO
|
||||
_ => {
|
||||
debug!(
|
||||
|
||||
@@ -19,6 +19,7 @@ use imbl::Vector;
|
||||
use matrix_sdk_base::latest_event::{is_suitable_for_latest_event, PossibleLatestEvent};
|
||||
use ruma::{
|
||||
events::{
|
||||
call::invite::SyncCallInviteEvent,
|
||||
policy::rule::{
|
||||
room::PolicyRuleRoomEventContent, server::PolicyRuleServerEventContent,
|
||||
user::PolicyRuleUserEventContent,
|
||||
@@ -106,6 +107,9 @@ pub enum TimelineItemContent {
|
||||
|
||||
/// An `m.poll.start` event.
|
||||
Poll(PollState),
|
||||
|
||||
/// An `m.call.invite` event
|
||||
CallInvite,
|
||||
}
|
||||
|
||||
impl TimelineItemContent {
|
||||
@@ -122,6 +126,9 @@ impl TimelineItemContent {
|
||||
PossibleLatestEvent::YesPoll(poll) => {
|
||||
Some(Self::from_suitable_latest_poll_event_content(poll))
|
||||
}
|
||||
PossibleLatestEvent::YesCallInvite(call_invite) => {
|
||||
Some(Self::from_suitable_latest_call_invite_content(call_invite))
|
||||
}
|
||||
PossibleLatestEvent::NoUnsupportedEventType => {
|
||||
// TODO: when we support state events in message previews, this will need change
|
||||
warn!("Found a state event cached as latest_event! ID={}", event.event_id());
|
||||
@@ -189,6 +196,15 @@ impl TimelineItemContent {
|
||||
}
|
||||
}
|
||||
|
||||
fn from_suitable_latest_call_invite_content(
|
||||
event: &SyncCallInviteEvent,
|
||||
) -> TimelineItemContent {
|
||||
match event {
|
||||
SyncCallInviteEvent::Original(_) => TimelineItemContent::CallInvite,
|
||||
SyncCallInviteEvent::Redacted(_) => TimelineItemContent::RedactedMessage,
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is of the [`Message`][Self::Message] variant, return the inner
|
||||
/// [`Message`].
|
||||
pub fn as_message(&self) -> Option<&Message> {
|
||||
@@ -228,6 +244,7 @@ impl TimelineItemContent {
|
||||
TimelineItemContent::FailedToParseMessageLike { .. }
|
||||
| TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
|
||||
TimelineItemContent::Poll(_) => "a poll",
|
||||
TimelineItemContent::CallInvite => "a call invite",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,6 +323,7 @@ impl TimelineItemContent {
|
||||
| Self::RedactedMessage
|
||||
| Self::Sticker(_)
|
||||
| Self::Poll(_)
|
||||
| Self::CallInvite
|
||||
| Self::UnableToDecrypt(_) => Self::RedactedMessage,
|
||||
Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(room_version)),
|
||||
Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
|
||||
|
||||
@@ -193,6 +193,7 @@ pub fn default_event_filter(event: &AnySyncTimelineEvent, room_version: &RoomVer
|
||||
| AnyMessageLikeEventContent::UnstablePollStart(
|
||||
UnstablePollStartEventContent::New(_),
|
||||
)
|
||||
| AnyMessageLikeEventContent::CallInvite(_)
|
||||
| AnyMessageLikeEventContent::RoomEncrypted(_) => true,
|
||||
|
||||
_ => false,
|
||||
|
||||
@@ -598,6 +598,9 @@ impl Timeline {
|
||||
TimelineItemContent::Poll(poll_state) => AnyMessageLikeEventContent::UnstablePollStart(
|
||||
UnstablePollStartEventContent::New(poll_state.into()),
|
||||
),
|
||||
TimelineItemContent::CallInvite => {
|
||||
error_return!("Retrying call events is not currently supported");
|
||||
}
|
||||
};
|
||||
|
||||
debug!("Retrying failed local echo");
|
||||
|
||||
@@ -60,7 +60,7 @@ use tokio::sync::{
|
||||
broadcast::{error::RecvError, Receiver, Sender},
|
||||
Mutex, RwLock,
|
||||
};
|
||||
use tracing::{error, trace};
|
||||
use tracing::{error, instrument, trace, warn};
|
||||
|
||||
use self::store::{EventCacheStore, MemoryStore};
|
||||
use crate::{client::ClientInner, Client, Room};
|
||||
@@ -77,13 +77,6 @@ pub enum EventCacheError {
|
||||
)]
|
||||
NotSubscribedYet,
|
||||
|
||||
/// The room hasn't been found in the client.
|
||||
///
|
||||
/// Technically, it's possible to request a `RoomEventCache` for a room that
|
||||
/// is not known to the client, leading to this error.
|
||||
#[error("Room {0} hasn't been found in the Client.")]
|
||||
RoomNotFound(OwnedRoomId),
|
||||
|
||||
/// The [`EventCache`] owns a weak reference to the [`Client`] it pertains
|
||||
/// to. It's possible this weak reference points to nothing anymore, at
|
||||
/// times where we try to use the client.
|
||||
@@ -206,10 +199,10 @@ impl EventCache {
|
||||
}
|
||||
|
||||
/// Return a room-specific view over the [`EventCache`].
|
||||
pub async fn for_room(
|
||||
pub(crate) async fn for_room(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<(RoomEventCache, Arc<EventCacheDropHandles>)> {
|
||||
) -> Result<(Option<RoomEventCache>, Arc<EventCacheDropHandles>)> {
|
||||
let Some(drop_handles) = self.inner.drop_handles.get().cloned() else {
|
||||
return Err(EventCacheError::NotSubscribedYet);
|
||||
};
|
||||
@@ -223,12 +216,16 @@ impl EventCache {
|
||||
///
|
||||
/// TODO: temporary for API compat, as the event cache should take care of
|
||||
/// its own store.
|
||||
#[instrument(skip(self, events))]
|
||||
pub async fn add_initial_events(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
events: Vec<SyncTimelineEvent>,
|
||||
) -> Result<()> {
|
||||
let room_cache = self.inner.for_room(room_id).await?;
|
||||
let Some(room_cache) = self.inner.for_room(room_id).await? else {
|
||||
warn!("unknown room, skipping");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// We could have received events during a previous sync; remove them all, since
|
||||
// we can't know where to insert the "initial events" with respect to
|
||||
@@ -270,6 +267,7 @@ impl EventCacheInner {
|
||||
}
|
||||
|
||||
/// Handles a single set of room updates at once.
|
||||
#[instrument(skip(self, updates))]
|
||||
async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
|
||||
// First, take the lock that indicates we're processing updates, to avoid
|
||||
// handling multiple updates concurrently.
|
||||
@@ -277,7 +275,10 @@ impl EventCacheInner {
|
||||
|
||||
// Left rooms.
|
||||
for (room_id, left_room_update) in updates.leave {
|
||||
let room = self.for_room(&room_id).await?;
|
||||
let Some(room) = self.for_room(&room_id).await? else {
|
||||
warn!(%room_id, "missing left room");
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Err(err) = room.inner.handle_left_room_update(left_room_update).await {
|
||||
// Non-fatal error, try to continue to the next room.
|
||||
@@ -287,7 +288,10 @@ impl EventCacheInner {
|
||||
|
||||
// Joined rooms.
|
||||
for (room_id, joined_room_update) in updates.join {
|
||||
let room = self.for_room(&room_id).await?;
|
||||
let Some(room) = self.for_room(&room_id).await? else {
|
||||
warn!(%room_id, "missing joined room");
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Err(err) = room.inner.handle_joined_room_update(joined_room_update).await {
|
||||
// Non-fatal error, try to continue to the next room.
|
||||
@@ -303,14 +307,15 @@ impl EventCacheInner {
|
||||
|
||||
/// Return a room-specific view over the [`EventCache`].
|
||||
///
|
||||
/// It may not be found, if the room isn't known to the client.
|
||||
async fn for_room(&self, room_id: &RoomId) -> Result<RoomEventCache> {
|
||||
/// It may not be found, if the room isn't known to the client, in which
|
||||
/// case it'll return None.
|
||||
async fn for_room(&self, room_id: &RoomId) -> Result<Option<RoomEventCache>> {
|
||||
// Fast path: the entry exists; let's acquire a read lock, it's cheaper than a
|
||||
// write lock.
|
||||
let by_room_guard = self.by_room.read().await;
|
||||
|
||||
match by_room_guard.get(room_id) {
|
||||
Some(room) => Ok(room.clone()),
|
||||
Some(room) => Ok(Some(room.clone())),
|
||||
|
||||
None => {
|
||||
// Slow-path: the entry doesn't exist; let's acquire a write lock.
|
||||
@@ -320,19 +325,18 @@ impl EventCacheInner {
|
||||
// In the meanwhile, some other caller might have obtained write access and done
|
||||
// the same, so check for existence again.
|
||||
if let Some(room) = by_room_guard.get(room_id) {
|
||||
return Ok(room.clone());
|
||||
return Ok(Some(room.clone()));
|
||||
}
|
||||
|
||||
let room = self
|
||||
.client()?
|
||||
.get_room(room_id)
|
||||
.ok_or_else(|| EventCacheError::RoomNotFound(room_id.to_owned()))?;
|
||||
let Some(room) = self.client()?.get_room(room_id) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let room_event_cache = RoomEventCache::new(room, self.store.clone());
|
||||
|
||||
by_room_guard.insert(room_id.to_owned(), room_event_cache.clone());
|
||||
|
||||
Ok(room_event_cache)
|
||||
Ok(Some(room_event_cache))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
//! High-level room API
|
||||
|
||||
use std::{borrow::Borrow, collections::BTreeMap, ops::Deref, time::Duration};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::{BTreeMap, HashMap},
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use eyeball::SharedObservable;
|
||||
use futures_core::Stream;
|
||||
@@ -79,10 +85,13 @@ pub use self::{
|
||||
member::{RoomMember, RoomMemberRole},
|
||||
messages::{Messages, MessagesOptions},
|
||||
};
|
||||
#[cfg(doc)]
|
||||
use crate::event_cache::EventCache;
|
||||
use crate::{
|
||||
attachment::AttachmentConfig,
|
||||
config::RequestConfig,
|
||||
error::WrongRoomState,
|
||||
event_cache::{self, EventCacheDropHandles, RoomEventCache},
|
||||
event_handler::{EventHandler, EventHandlerDropGuard, EventHandlerHandle, SyncEvent},
|
||||
media::{MediaFormat, MediaRequest},
|
||||
notification_settings::{IsEncrypted, IsOneToOne, RoomNotificationMode},
|
||||
@@ -1586,8 +1595,6 @@ impl Room {
|
||||
/// Run /keys/query requests for all the non-tracked users.
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
async fn query_keys_for_untracked_users(&self) -> Result<()> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let olm = self.client.olm_machine().await;
|
||||
let olm = olm.as_ref().expect("Olm machine wasn't started");
|
||||
|
||||
@@ -1871,6 +1878,19 @@ impl Room {
|
||||
Ok(event.for_user(user_id).into())
|
||||
}
|
||||
|
||||
/// Gets a map with the `UserId` of users with power levels other than `0`
|
||||
/// and this power level.
|
||||
pub async fn users_with_power_levels(&self) -> HashMap<OwnedUserId, i64> {
|
||||
let power_levels = self.room_power_levels().await.ok();
|
||||
let mut user_power_levels = HashMap::<OwnedUserId, i64>::new();
|
||||
if let Some(power_levels) = power_levels {
|
||||
for (id, level) in power_levels.users.into_iter() {
|
||||
user_power_levels.insert(id, level.into());
|
||||
}
|
||||
}
|
||||
user_power_levels
|
||||
}
|
||||
|
||||
/// Sets the name of this room.
|
||||
pub async fn set_name(&self, name: String) -> Result<send_state_event::v3::Response> {
|
||||
self.send_state_event(RoomNameEventContent::new(name)).await
|
||||
@@ -2570,6 +2590,20 @@ impl Room {
|
||||
self.client.send(request, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the [`RoomEventCache`] associated to this room, assuming the
|
||||
/// global [`EventCache`] has been enabled for subscription.
|
||||
pub async fn event_cache(
|
||||
&self,
|
||||
) -> event_cache::Result<(RoomEventCache, Arc<EventCacheDropHandles>)> {
|
||||
let global_event_cache = self.client.event_cache();
|
||||
|
||||
global_event_cache.for_room(self.room_id()).await.map(|(maybe_room, drop_handles)| {
|
||||
// SAFETY: the `RoomEventCache` must always been found, since we're constructing
|
||||
// from a `Room`.
|
||||
(maybe_room.unwrap(), drop_handles)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Details of the (latest) invite.
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::time::Duration;
|
||||
use assert_matches2::{assert_let, assert_matches};
|
||||
use matrix_sdk::{
|
||||
event_cache::{EventCacheError, RoomEventCacheUpdate},
|
||||
test_utils::{logged_in_client, logged_in_client_with_server},
|
||||
test_utils::logged_in_client_with_server,
|
||||
};
|
||||
use matrix_sdk_common::deserialized_responses::SyncTimelineEvent;
|
||||
use matrix_sdk_test::{
|
||||
@@ -32,46 +32,37 @@ fn assert_event_matches_msg(event: &SyncTimelineEvent, expected: &str) {
|
||||
|
||||
#[async_test]
|
||||
async fn test_must_explicitly_subscribe() {
|
||||
let client = logged_in_client(None).await;
|
||||
let (client, server) = logged_in_client_with_server().await;
|
||||
|
||||
let event_cache = client.event_cache();
|
||||
let room_id = room_id!("!omelette:fromage.fr");
|
||||
|
||||
// Make sure the client is aware of the room.
|
||||
{
|
||||
let mut sync_builder = SyncResponseBuilder::new();
|
||||
sync_builder.add_joined_room(JoinedRoomBuilder::new(room_id));
|
||||
let response_body = sync_builder.build_json_sync_response();
|
||||
|
||||
mock_sync(&server, response_body, None).await;
|
||||
client.sync_once(Default::default()).await.unwrap();
|
||||
server.reset().await;
|
||||
}
|
||||
|
||||
// If I create a room event subscriber for a room before subscribing the event
|
||||
// cache,
|
||||
let room_id = room_id!("!omelette:fromage.fr");
|
||||
let result = event_cache.for_room(room_id).await;
|
||||
let room = client.get_room(room_id).unwrap();
|
||||
let result = room.event_cache().await;
|
||||
|
||||
// Then it fails, because one must explicitly call `.subscribe()` on the event
|
||||
// cache.
|
||||
assert_matches!(result, Err(EventCacheError::NotSubscribedYet));
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_cant_subscribe_to_unknown_room() {
|
||||
let client = logged_in_client(None).await;
|
||||
|
||||
let event_cache = client.event_cache();
|
||||
|
||||
// First, subscribe to the event cache.
|
||||
event_cache.subscribe().unwrap();
|
||||
|
||||
// If I create a room event subscriber for a room unknown to the client,
|
||||
let room_id = room_id!("!omelette:fromage.fr");
|
||||
let result = event_cache.for_room(room_id).await;
|
||||
|
||||
// Then it fails, because this particular room is still unknown to the client.
|
||||
assert_let!(Err(EventCacheError::RoomNotFound(observed_room_id)) = result);
|
||||
assert_eq!(observed_room_id, room_id);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_add_initial_events() {
|
||||
let (client, server) = logged_in_client_with_server().await;
|
||||
|
||||
let event_cache = client.event_cache();
|
||||
|
||||
// Immediately subscribe the event cache to sync updates.
|
||||
event_cache.subscribe().unwrap();
|
||||
client.event_cache().subscribe().unwrap();
|
||||
|
||||
// If I sync and get informed I've joined The Room, but with no events,
|
||||
let room_id = room_id!("!omelette:fromage.fr");
|
||||
@@ -86,7 +77,8 @@ async fn test_add_initial_events() {
|
||||
|
||||
// If I create a room event subscriber,
|
||||
|
||||
let (room_event_cache, _drop_handles) = event_cache.for_room(room_id).await.unwrap();
|
||||
let room = client.get_room(room_id).unwrap();
|
||||
let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
|
||||
let (events, mut subscriber) = room_event_cache.subscribe().await.unwrap();
|
||||
|
||||
// Then at first it's empty, and the subscriber doesn't yield anything.
|
||||
@@ -131,7 +123,8 @@ async fn test_add_initial_events() {
|
||||
|
||||
// XXX: when we get rid of `add_initial_events`, we can keep this test as a
|
||||
// smoke test for the event cache.
|
||||
event_cache
|
||||
client
|
||||
.event_cache()
|
||||
.add_initial_events(
|
||||
room_id,
|
||||
vec![SyncTimelineEvent::new(sync_timeline_event!({
|
||||
|
||||
@@ -787,3 +787,33 @@ async fn get_power_level_for_user() {
|
||||
room.get_user_power_level(user_id!("@non-existing:localhost")).await.unwrap();
|
||||
assert_eq!(power_level_unknown, 0);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn get_users_with_power_levels() {
|
||||
let (client, server) = logged_in_client_with_server().await;
|
||||
|
||||
mock_sync(&server, &*test_json::sync::SYNC_ADMIN_AND_MOD, None).await;
|
||||
|
||||
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap();
|
||||
|
||||
let users_with_power_levels = room.users_with_power_levels().await;
|
||||
assert_eq!(users_with_power_levels.len(), 2);
|
||||
assert_eq!(users_with_power_levels[user_id!("@admin:localhost")], 100);
|
||||
assert_eq!(users_with_power_levels[user_id!("@mod:localhost")], 50);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn get_users_with_power_levels_is_empty_if_power_level_info_is_not_available() {
|
||||
let (client, server) = logged_in_client_with_server().await;
|
||||
|
||||
mock_sync(&server, &*test_json::INVITE_SYNC, None).await;
|
||||
|
||||
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
// The room doesn't have any power level info
|
||||
let room = client.get_room(room_id!("!696r7674:example.com")).unwrap();
|
||||
|
||||
assert!(room.users_with_power_levels().await.is_empty());
|
||||
}
|
||||
|
||||
@@ -1525,3 +1525,192 @@ pub static VOIP_SYNC: Lazy<JsonValue> = Lazy::new(|| {
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
pub static SYNC_ADMIN_AND_MOD: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"device_one_time_keys_count": {},
|
||||
"next_batch": "s526_47314_0_7_1_1_1_11444_1",
|
||||
"device_lists": {
|
||||
"changed": [
|
||||
"@admin:example.org"
|
||||
],
|
||||
"left": []
|
||||
},
|
||||
"rooms": {
|
||||
"invite": {},
|
||||
"join": {
|
||||
*DEFAULT_TEST_ROOM_ID: {
|
||||
"summary": {
|
||||
"m.heroes": [
|
||||
"@example2:localhost"
|
||||
],
|
||||
"m.joined_member_count": 2,
|
||||
"m.invited_member_count": 0
|
||||
},
|
||||
"account_data": {
|
||||
"events": []
|
||||
},
|
||||
"ephemeral": {
|
||||
"events": []
|
||||
},
|
||||
"state": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"join_rule": "public"
|
||||
},
|
||||
"event_id": "$15139375514WsgmR:localhost",
|
||||
"origin_server_ts": 151393755000000_u64,
|
||||
"sender": "@admin:localhost",
|
||||
"state_key": "",
|
||||
"type": "m.room.join_rules",
|
||||
"unsigned": {
|
||||
"age": 7034220
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": "admin",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "$151800140517rfvjc:localhost",
|
||||
"membership": "join",
|
||||
"origin_server_ts": 151800140000000_u64,
|
||||
"sender": "@admin:localhost",
|
||||
"state_key": "@admin:localhost",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {
|
||||
"age": 297036,
|
||||
"replaces_state": "$151800111315tsynI:localhost"
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": "mod",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "$151800140518rfvjc:localhost",
|
||||
"membership": "join",
|
||||
"origin_server_ts": 1518001450000000_u64,
|
||||
"sender": "@mod:localhost",
|
||||
"state_key": "@mod:localhost",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {
|
||||
"age": 297035,
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"history_visibility": "shared"
|
||||
},
|
||||
"event_id": "$15139375515VaJEY:localhost",
|
||||
"origin_server_ts": 151393755000000_u64,
|
||||
"sender": "@admin:localhost",
|
||||
"state_key": "",
|
||||
"type": "m.room.history_visibility",
|
||||
"unsigned": {
|
||||
"age": 703422
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"creator": "@example:localhost"
|
||||
},
|
||||
"event_id": "$15139375510KUZHi:localhost",
|
||||
"origin_server_ts": 151393755000000_u64,
|
||||
"sender": "@admin:localhost",
|
||||
"state_key": "",
|
||||
"type": "m.room.create",
|
||||
"unsigned": {
|
||||
"age": 703422
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"topic": "room topic"
|
||||
},
|
||||
"event_id": "$151957878228ssqrJ:localhost",
|
||||
"origin_server_ts": 151957878000000_u64,
|
||||
"sender": "@admin:localhost",
|
||||
"state_key": "",
|
||||
"type": "m.room.topic",
|
||||
"unsigned": {
|
||||
"age": 1392989709,
|
||||
"prev_content": {
|
||||
"topic": "test"
|
||||
},
|
||||
"prev_sender": "@example:localhost",
|
||||
"replaces_state": "$151957069225EVYKm:localhost"
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"ban": 50,
|
||||
"events": {
|
||||
"m.room.avatar": 50,
|
||||
"m.room.canonical_alias": 50,
|
||||
"m.room.history_visibility": 100,
|
||||
"m.room.name": 50,
|
||||
"m.room.power_levels": 100
|
||||
},
|
||||
"events_default": 0,
|
||||
"invite": 0,
|
||||
"kick": 50,
|
||||
"redact": 50,
|
||||
"state_default": 50,
|
||||
"users": {
|
||||
"@admin:localhost": 100,
|
||||
"@mod:localhost": 50
|
||||
},
|
||||
"users_default": 0
|
||||
},
|
||||
"event_id": "$15139375512JaHAW:localhost",
|
||||
"origin_server_ts": 151393755000000_u64,
|
||||
"sender": "@admin:localhost",
|
||||
"state_key": "",
|
||||
"type": "m.room.power_levels",
|
||||
"unsigned": {
|
||||
"age": 703422
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"timeline": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"body": "baba",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "<strong>baba</strong>",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$152037280074GZeOm:localhost",
|
||||
"origin_server_ts": 152037280000000_u64,
|
||||
"sender": "@admin:localhost",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 598971425
|
||||
}
|
||||
}
|
||||
],
|
||||
"limited": true,
|
||||
"prev_batch": "t392-516_47314_0_7_1_1_1_11444_1"
|
||||
},
|
||||
"unread_notifications": {
|
||||
"highlight_count": 0,
|
||||
"notification_count": 11
|
||||
}
|
||||
}
|
||||
},
|
||||
"leave": {}
|
||||
},
|
||||
"to_device": {
|
||||
"events": []
|
||||
},
|
||||
"presence": {
|
||||
"events": []
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user