chore: Merge remote-tracking branch 'origin/main' into b-featureflag-timeline

This commit is contained in:
Benjamin Kampmann
2022-05-05 10:33:56 +02:00
14 changed files with 872 additions and 130 deletions

View File

@@ -55,7 +55,10 @@ ruma = { version = "0.6.1", features = ["client-api-c", "signatures"] }
[dev-dependencies]
futures = { version = "0.3.21", default-features = false, features = ["executor"] }
tracing = { version = "0.1.26", features = ["log"] }
http = "0.2.6"
assign = "1.1.1"
env_logger = "0.9.0"
matrix-sdk-test = { version = "0.4.0", path = "../matrix-sdk-test" }
tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread", "macros"] }

View File

@@ -1301,12 +1301,15 @@ impl Default for BaseClient {
#[cfg(test)]
mod tests {
use matrix_sdk_test::{async_test, EventBuilder};
use ruma::{room_id, user_id};
use matrix_sdk_test::{async_test, response_from_file, EventBuilder};
use ruma::{
api::{client as api, IncomingResponse},
room_id, user_id,
};
use serde_json::json;
use super::BaseClient;
use crate::{RoomType, Session};
use crate::{DisplayName, RoomType, Session};
#[async_test]
async fn invite_after_leaving() {
@@ -1363,4 +1366,100 @@ mod tests {
client.receive_sync_response(response).await.unwrap();
assert_eq!(client.get_room(room_id).unwrap().room_type(), RoomType::Invited);
}
#[async_test]
async fn invite_displayname_integration_test() {
let user_id = user_id!("@alice:example.org");
let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
let client = BaseClient::new();
client
.restore_login(Session {
access_token: "token".to_owned(),
user_id: user_id.to_owned(),
device_id: "FOOBAR".into(),
})
.await
.unwrap();
let response = api::sync::sync_events::v3::Response::try_from_http_response(response_from_file(&json!({
"next_batch": "asdkl;fjasdkl;fj;asdkl;f",
"device_one_time_keys_count": {
"signed_curve25519": 50u64
},
"device_unused_fallback_key_types": [
"signed_curve25519"
],
"rooms": {
"invite": {
"!ithpyNKDtmhneaTQja:example.org": {
"invite_state": {
"events": [
{
"content": {
"creator": "@test:example.org",
"room_version": "9"
},
"sender": "@test:example.org",
"state_key": "",
"type": "m.room.create"
},
{
"content": {
"join_rule": "invite"
},
"sender": "@test:example.org",
"state_key": "",
"type": "m.room.join_rules"
},
{
"content": {
"algorithm": "m.megolm.v1.aes-sha2"
},
"sender": "@test:example.org",
"state_key": "",
"type": "m.room.encryption"
},
{
"content": {
"avatar_url": "mxc://example.org/dcBBDwuWEUrjfrOchvkirUST",
"displayname": "Kyra",
"membership": "join"
},
"sender": "@test:example.org",
"state_key": "@test:example.org",
"type": "m.room.member"
},
{
"content": {
"avatar_url": "mxc://example.org/ABFEXSDrESxovWwEnCYdNcHT",
"displayname": "alice",
"is_direct": true,
"membership": "invite"
},
"origin_server_ts": 1650878657984u64,
"sender": "@test:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 14u64
},
"event_id": "$fLDqltg9Puj-kWItLSFVHPGN4YkgpYQf2qImPzdmgrE"
}
]
}
}
}
}
}))).expect("static json doesn't fail to parse");
client.receive_sync_response(response).await.unwrap();
let room = client.get_room(room_id).expect("Room not found");
assert_eq!(room.room_type(), RoomType::Invited);
assert_eq!(
room.display_name().await.expect("fetching display name failed"),
DisplayName::Calculated("Kyra".to_owned())
);
}
}

View File

@@ -40,5 +40,5 @@ pub use client::BaseClient;
pub use http;
#[cfg(feature = "e2e-encryption")]
pub use matrix_sdk_crypto as crypto;
pub use rooms::{Room, RoomInfo, RoomMember, RoomType};
pub use rooms::{DisplayName, Room, RoomInfo, RoomMember, RoomType};
pub use store::{StateChanges, StateStore, Store, StoreError};

View File

@@ -18,17 +18,19 @@ use ruma::{
events::{
presence::PresenceEvent,
room::{
member::{MembershipState, OriginalSyncRoomMemberEvent, RoomMemberEventContent},
member::{MembershipState, RoomMemberEventContent},
power_levels::SyncRoomPowerLevelsEvent,
},
},
MxcUri, UserId,
};
use crate::deserialized_responses::MemberEvent;
/// A member of a room.
#[derive(Clone, Debug)]
pub struct RoomMember {
pub(crate) event: Arc<OriginalSyncRoomMemberEvent>,
pub(crate) event: Arc<MemberEvent>,
pub(crate) profile: Arc<Option<RoomMemberEventContent>>,
#[allow(dead_code)]
pub(crate) presence: Arc<Option<PresenceEvent>>,
@@ -41,7 +43,7 @@ pub struct RoomMember {
impl RoomMember {
/// Get the unique user id of this member.
pub fn user_id(&self) -> &UserId {
&self.event.state_key
self.event.user_id()
}
/// Get the display name of the member if there is one.
@@ -49,7 +51,7 @@ impl RoomMember {
if let Some(p) = self.profile.as_ref() {
p.displayname.as_deref()
} else {
self.event.content.displayname.as_deref()
self.event.content().displayname.as_deref()
}
}
@@ -69,7 +71,7 @@ impl RoomMember {
pub fn avatar_url(&self) -> Option<&MxcUri> {
match self.profile.as_ref() {
Some(p) => p.avatar_url.as_deref(),
None => self.event.content.avatar_url.as_deref(),
None => self.event.content().avatar_url.as_deref(),
}
}
@@ -112,7 +114,7 @@ impl RoomMember {
if let Some(p) = self.profile.as_ref() {
&p.membership
} else {
&self.event.content.membership
&self.event.content().membership
}
}
}

View File

@@ -1,7 +1,7 @@
mod members;
mod normal;
use std::{cmp::max, collections::HashSet};
use std::{cmp::max, collections::HashSet, fmt};
pub use members::RoomMember;
pub use normal::{Room, RoomInfo, RoomType};
@@ -18,6 +18,36 @@ use ruma::{
};
use serde::{Deserialize, Serialize};
/// The name of the room, either from the metadata or calculaetd
/// according to [matrix specification](https://matrix.org/docs/spec/client_server/latest#calculating-the-display-name-for-a-room)
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum DisplayName {
/// The room has been named explicitly as
Named(String),
/// The room has a canonical alias that should be used
Aliased(String),
/// The room has not given an explicit name but a name could be
/// calculated
Calculated(String),
/// The room doesn't have a name right now, but used to have one
/// e.g. because it was a DM and everyone has left the room
EmptyWas(String),
/// No useful name could be calculated or ever found
Empty,
}
impl fmt::Display for DisplayName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DisplayName::Named(s) | DisplayName::Calculated(s) | DisplayName::Aliased(s) => {
write!(f, "{}", s)
}
DisplayName::EmptyWas(s) => write!(f, "Empty Room (was {})", s),
DisplayName::Empty => write!(f, "Empty Room"),
}
}
}
/// A base room info struct that is the backbone of normal as well as stripped
/// rooms. Holds all the state events that are important to present a room to
/// users.
@@ -61,7 +91,7 @@ impl BaseRoomInfo {
joined_member_count: u64,
invited_member_count: u64,
heroes: Vec<RoomMember>,
) -> String {
) -> DisplayName {
calculate_room_name(
joined_member_count,
invited_member_count,
@@ -190,14 +220,12 @@ impl Default for BaseRoomInfo {
}
}
/// Calculate room name according to step 3 of the [naming algorithm.][spec]
///
/// [spec]: <https://matrix.org/docs/spec/client_server/latest#calculating-the-display-name-for-a-room>
/// Calculate room name according to step 3 of the [naming algorithm.]
fn calculate_room_name(
joined_member_count: u64,
invited_member_count: u64,
heroes: Vec<&str>,
) -> String {
) -> DisplayName {
let heroes_count = heroes.len() as u64;
let invited_joined = invited_member_count + joined_member_count;
let invited_joined_minus_one = invited_joined.saturating_sub(1);
@@ -221,12 +249,12 @@ fn calculate_room_name(
// User is alone.
if invited_joined <= 1 {
if names.is_empty() {
"Empty room".to_owned()
DisplayName::Empty
} else {
format!("Empty room (was {})", names)
DisplayName::EmptyWas(names)
}
} else {
names
DisplayName::Calculated(names)
}
}
@@ -237,33 +265,33 @@ mod tests {
fn test_calculate_room_name() {
let mut actual = calculate_room_name(2, 0, vec!["a"]);
assert_eq!("a", actual);
assert_eq!(DisplayName::Calculated("a".to_owned()), actual);
actual = calculate_room_name(3, 0, vec!["a", "b"]);
assert_eq!("a, b", actual);
assert_eq!(DisplayName::Calculated("a, b".to_owned()), actual);
actual = calculate_room_name(4, 0, vec!["a", "b", "c"]);
assert_eq!("a, b, c", actual);
assert_eq!(DisplayName::Calculated("a, b, c".to_owned()), actual);
actual = calculate_room_name(5, 0, vec!["a", "b", "c"]);
assert_eq!("a, b, c, and 2 others", actual);
assert_eq!(DisplayName::Calculated("a, b, c, and 2 others".to_owned()), actual);
actual = calculate_room_name(0, 0, vec![]);
assert_eq!("Empty room", actual);
assert_eq!(DisplayName::Empty, actual);
actual = calculate_room_name(1, 0, vec![]);
assert_eq!("Empty room", actual);
assert_eq!(DisplayName::Empty, actual);
actual = calculate_room_name(0, 1, vec![]);
assert_eq!("Empty room", actual);
assert_eq!(DisplayName::Empty, actual);
actual = calculate_room_name(1, 0, vec!["a"]);
assert_eq!("Empty room (was a)", actual);
assert_eq!(DisplayName::EmptyWas("a".to_owned()), actual);
actual = calculate_room_name(1, 0, vec!["a", "b"]);
assert_eq!("Empty room (was a, b)", actual);
assert_eq!(DisplayName::EmptyWas("a, b".to_owned()), actual);
actual = calculate_room_name(1, 0, vec!["a", "b", "c"]);
assert_eq!("Empty room (was a, b, c)", actual);
assert_eq!(DisplayName::EmptyWas("a, b, c".to_owned()), actual);
}
}

View File

@@ -46,7 +46,7 @@ use ruma::{
};
use serde::{Deserialize, Serialize};
use super::{BaseRoomInfo, RoomMember};
use super::{BaseRoomInfo, DisplayName, RoomMember};
use crate::{
deserialized_responses::UnreadNotificationsCount,
store::{Result as StoreResult, StateStore},
@@ -276,7 +276,7 @@ impl Room {
/// The display name is calculated according to [this algorithm][spec].
///
/// [spec]: <https://matrix.org/docs/spec/client_server/latest#calculating-the-display-name-for-a-room>
pub async fn display_name(&self) -> StoreResult<String> {
pub async fn display_name(&self) -> StoreResult<DisplayName> {
self.calculate_name().await
}
@@ -338,24 +338,19 @@ impl Room {
Ok(members)
}
async fn calculate_name(&self) -> StoreResult<String> {
async fn calculate_name(&self) -> StoreResult<DisplayName> {
let summary = {
let inner = self.inner.read().unwrap();
if let Some(name) = &inner.base_info.name {
let name = name.trim();
return Ok(name.to_owned());
return Ok(DisplayName::Named(name.to_owned()));
} else if let Some(alias) = &inner.base_info.canonical_alias {
let alias = alias.alias().trim();
return Ok(alias.to_owned());
return Ok(DisplayName::Aliased(alias.to_owned()));
}
inner.summary.clone()
};
// TODO what should we do here? We have correct counts only if lazy
// loading is used.
let joined = summary.joined_member_count;
let invited = summary.invited_member_count;
let heroes_count = summary.heroes.len() as u64;
let is_own_member = |m: &RoomMember| m.user_id() == &*self.own_user_id;
let is_own_user_id = |u: &str| u == self.own_user_id().as_str();
@@ -377,11 +372,27 @@ impl Room {
members?
};
let (joined, invited) = match self.room_type() {
RoomType::Invited => {
// when we were invited we don't have a proper summary, we have to do best
// guessing
(members.len() as u64, 1u64)
}
RoomType::Joined if summary.joined_member_count == 0 => {
// joined but the summary is not completed yet
(
(members.len() as u64) + 1, // we've taken ourselves out of the count
summary.invited_member_count,
)
}
_ => (summary.joined_member_count, summary.invited_member_count),
};
tracing::debug!(
room_id = self.room_id().as_str(),
own_user = self.own_user_id.as_str(),
heroes_count = heroes_count,
heroes = ?summary.heroes,
joined, invited,
heroes = ?members,
"Calculating name for a room",
);
@@ -444,14 +455,18 @@ impl Room {
.store
.get_users_with_display_name(
self.room_id(),
member_event.content.displayname.as_deref().unwrap_or_else(|| user_id.localpart()),
member_event
.content()
.displayname
.as_deref()
.unwrap_or_else(|| user_id.localpart()),
)
.await?
.len()
> 1;
Ok(Some(RoomMember {
event: member_event.into(),
event: Arc::new(member_event),
profile: profile.into(),
presence: presence.into(),
power_levels: power.into(),
@@ -735,3 +750,212 @@ impl RoomInfo {
self.base_info.create.as_ref().map(|c| &c.room_version)
}
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use assign::assign;
use ruma::{
event_id,
events::{
room::member::{
MembershipState, OriginalSyncRoomMemberEvent, RoomMemberEventContent,
StrippedRoomMemberEvent,
},
StateUnsigned,
},
room_id, user_id, MilliSecondsSinceUnixEpoch, RoomAliasId,
};
use super::*;
use crate::store::{MemoryStore, StateChanges};
fn make_room(room_type: RoomType) -> (Arc<MemoryStore>, Room) {
let store = Arc::new(MemoryStore::new());
let user_id = user_id!("@me:example.org");
let room_id = room_id!("!test:localhost");
(store.clone(), Room::new(user_id, store, room_id, room_type))
}
fn make_stripped_member_event(user_id: &UserId, name: &str) -> StrippedRoomMemberEvent {
StrippedRoomMemberEvent {
content: assign!(RoomMemberEventContent::new(MembershipState::Join), {
displayname: Some(name.to_owned())
}),
sender: user_id.to_owned(),
state_key: user_id.to_owned(),
}
}
fn make_member_event(user_id: &UserId, name: &str) -> OriginalSyncRoomMemberEvent {
OriginalSyncRoomMemberEvent {
content: assign!(RoomMemberEventContent::new(MembershipState::Join), {
displayname: Some(name.to_owned())
}),
sender: user_id.to_owned(),
state_key: user_id.to_owned(),
event_id: event_id!("$h29iv0s1:example.com").to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch(208u32.into()),
unsigned: StateUnsigned::default(),
}
}
#[tokio::test]
async fn test_display_name_default() {
let _ = env_logger::try_init();
let (_, room) = make_room(RoomType::Joined);
assert_eq!(room.display_name().await.unwrap(), DisplayName::Empty);
// has precedence
room.inner.write().unwrap().base_info.canonical_alias =
Some(RoomAliasId::parse("#test:example.com").unwrap());
assert_eq!(room.display_name().await.unwrap(), DisplayName::Aliased("test".to_owned()));
// has precedence
room.inner.write().unwrap().base_info.name = Some("Test Room".to_owned());
assert_eq!(room.display_name().await.unwrap(), DisplayName::Named("Test Room".to_owned()));
let (_, room) = make_room(RoomType::Invited);
assert_eq!(room.display_name().await.unwrap(), DisplayName::Empty);
// has precedence
room.inner.write().unwrap().base_info.canonical_alias =
Some(RoomAliasId::parse("#test:example.com").unwrap());
assert_eq!(room.display_name().await.unwrap(), DisplayName::Aliased("test".to_owned()));
// has precedence
room.inner.write().unwrap().base_info.name = Some("Test Room".to_owned());
assert_eq!(room.display_name().await.unwrap(), DisplayName::Named("Test Room".to_owned()));
}
#[tokio::test]
async fn test_display_name_dm_invited() {
let _ = env_logger::try_init();
let (store, room) = make_room(RoomType::Invited);
let room_id = room_id!("!test:localhost");
let matthew = user_id!("@matthew:example.org");
let me = user_id!("@me:example.org");
let mut changes = StateChanges::new("".to_owned());
let summary = assign!(RumaSummary::new(), {
heroes: vec![me.to_string(), matthew.to_string()],
});
changes.add_stripped_member(room_id, make_stripped_member_event(matthew, "Matthew"));
changes.add_stripped_member(room_id, make_stripped_member_event(me, "Me"));
store.save_changes(&changes).await.unwrap();
room.inner.write().unwrap().update_summary(&summary);
assert_eq!(
room.display_name().await.unwrap(),
DisplayName::Calculated("Matthew".to_owned())
);
}
#[tokio::test]
async fn test_display_name_dm_invited_no_heroes() {
let _ = env_logger::try_init();
let (store, room) = make_room(RoomType::Invited);
let room_id = room_id!("!test:localhost");
let matthew = user_id!("@matthew:example.org");
let me = user_id!("@me:example.org");
let mut changes = StateChanges::new("".to_owned());
changes.add_stripped_member(room_id, make_stripped_member_event(matthew, "Matthew"));
changes.add_stripped_member(room_id, make_stripped_member_event(me, "Me"));
store.save_changes(&changes).await.unwrap();
assert_eq!(
room.display_name().await.unwrap(),
DisplayName::Calculated("Matthew".to_owned())
);
}
#[tokio::test]
async fn test_display_name_dm_joined() {
let _ = env_logger::try_init();
let (store, room) = make_room(RoomType::Joined);
let room_id = room_id!("!test:localhost");
let matthew = user_id!("@matthew:example.org");
let me = user_id!("@me:example.org");
let mut changes = StateChanges::new("".to_owned());
let summary = assign!(RumaSummary::new(), {
joined_member_count: Some(2u32.into()),
heroes: vec![me.to_string(), matthew.to_string()],
});
changes
.members
.entry(room_id.to_owned())
.or_default()
.insert(matthew.to_owned(), make_member_event(matthew, "Matthew"));
changes
.members
.entry(room_id.to_owned())
.or_default()
.insert(me.to_owned(), make_member_event(me, "Me"));
store.save_changes(&changes).await.unwrap();
room.inner.write().unwrap().update_summary(&summary);
assert_eq!(
room.display_name().await.unwrap(),
DisplayName::Calculated("Matthew".to_owned())
);
}
#[tokio::test]
async fn test_display_name_dm_joined_no_heroes() {
let _ = env_logger::try_init();
let (store, room) = make_room(RoomType::Joined);
let room_id = room_id!("!test:localhost");
let matthew = user_id!("@matthew:example.org");
let me = user_id!("@me:example.org");
let mut changes = StateChanges::new("".to_owned());
changes
.members
.entry(room_id.to_owned())
.or_default()
.insert(matthew.to_owned(), make_member_event(matthew, "Matthew"));
changes
.members
.entry(room_id.to_owned())
.or_default()
.insert(me.to_owned(), make_member_event(me, "Me"));
store.save_changes(&changes).await.unwrap();
assert_eq!(
room.display_name().await.unwrap(),
DisplayName::Calculated("Matthew".to_owned())
);
}
#[tokio::test]
async fn test_display_name_dm_alone() {
let _ = env_logger::try_init();
let (store, room) = make_room(RoomType::Joined);
let room_id = room_id!("!test:localhost");
let matthew = user_id!("@matthew:example.org");
let me = user_id!("@me:example.org");
let mut changes = StateChanges::new("".to_owned());
let summary = assign!(RumaSummary::new(), {
joined_member_count: Some(1u32.into()),
heroes: vec![me.to_string(), matthew.to_string()],
});
changes
.members
.entry(room_id.to_owned())
.or_default()
.insert(matthew.to_owned(), make_member_event(matthew, "Matthew"));
changes
.members
.entry(room_id.to_owned())
.or_default()
.insert(me.to_owned(), make_member_event(me, "Me"));
store.save_changes(&changes).await.unwrap();
room.inner.write().unwrap().update_summary(&summary);
assert_eq!(room.display_name().await.unwrap(), DisplayName::EmptyWas("Matthew".to_owned()));
}
}

View File

@@ -14,7 +14,7 @@
use std::collections::{BTreeMap, BTreeSet};
use matrix_sdk_common::deserialized_responses::AmbiguityChange;
use matrix_sdk_common::deserialized_responses::{AmbiguityChange, MemberEvent};
use ruma::{
events::room::member::{MembershipState, OriginalSyncRoomMemberEvent},
OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
@@ -158,13 +158,13 @@ impl AmbiguityCache {
let old_event = if let Some(m) =
changes.members.get(room_id).and_then(|m| m.get(&member_event.state_key))
{
Some(m.clone())
Some(MemberEvent::Original(m.clone()))
} else {
self.store.get_member_event(room_id, &member_event.state_key).await?
};
let old_display_name = if let Some(event) = old_event {
if matches!(event.content.membership, Join | Invite) {
if matches!(event.content().membership, Join | Invite) {
let display_name = if let Some(d) = changes
.profiles
.get(room_id)
@@ -180,10 +180,10 @@ impl AmbiguityCache {
{
Some(d)
} else {
event.content.displayname.clone()
event.content().displayname.clone()
};
Some(display_name.unwrap_or_else(|| event.state_key.localpart().to_owned()))
Some(display_name.unwrap_or_else(|| event.user_id().localpart().to_owned()))
} else {
None
}

View File

@@ -265,6 +265,20 @@ macro_rules! statestore_integration_tests {
serde_json::from_value(event).unwrap()
}
fn stripped_membership_event() -> StrippedRoomMemberEvent {
custom_stripped_membership_event(user_id())
}
fn custom_stripped_membership_event(
user_id: &UserId,
) -> StrippedRoomMemberEvent {
StrippedRoomMemberEvent {
content: RoomMemberEventContent::new(MembershipState::Join),
sender: user_id.to_owned(),
state_key: user_id.to_owned(),
}
}
fn membership_event() -> OriginalSyncRoomMemberEvent {
custom_membership_event(user_id(), event_id!("$h29iv0s8:example.com").to_owned())
}
@@ -281,7 +295,6 @@ macro_rules! statestore_integration_tests {
state_key: user_id.to_owned(),
unsigned: StateUnsigned::default(),
}
}
#[async_test]
@@ -344,6 +357,27 @@ macro_rules! statestore_integration_tests {
assert!(!members.is_empty(), "We expected to find members for the room")
}
#[async_test]
async fn test_stripped_member_saving() {
let store = get_store().await.unwrap();
let room_id = room_id!("!test_stripped_member_saving:localhost");
let user_id = user_id();
assert!(store.get_member_event(room_id, user_id).await.unwrap().is_none());
let mut changes = StateChanges::default();
changes
.stripped_members
.entry(room_id.to_owned())
.or_default()
.insert(user_id.to_owned(), stripped_membership_event());
store.save_changes(&changes).await.unwrap();
assert!(store.get_member_event(room_id, user_id).await.unwrap().is_some());
let members = store.get_user_ids(room_id).await.unwrap();
assert!(!members.is_empty(), "We expected to find members for the room")
}
#[async_test]
async fn test_power_level_saving() {
let store = get_store().await.unwrap();

View File

@@ -51,7 +51,10 @@ use ruma::{
#[cfg(feature = "experimental-timeline")]
use super::BoxStream;
use super::{Result, RoomInfo, StateChanges, StateStore};
use crate::media::{MediaRequest, UniqueKey};
use crate::{
deserialized_responses::MemberEvent,
media::{MediaRequest, UniqueKey},
};
#[cfg(feature = "experimental-timeline")]
use crate::{deserialized_responses::SyncRoomEvent, StoreError};
@@ -79,6 +82,8 @@ pub struct MemoryStore {
DashMap<OwnedRoomId, DashMap<StateEventType, DashMap<String, Raw<AnyStrippedStateEvent>>>>,
>,
stripped_members: Arc<DashMap<OwnedRoomId, DashMap<OwnedUserId, StrippedRoomMemberEvent>>>,
stripped_joined_user_ids: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
stripped_invited_user_ids: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
presence: Arc<DashMap<OwnedUserId, Raw<PresenceEvent>>>,
room_user_receipts:
Arc<DashMap<OwnedRoomId, DashMap<String, DashMap<OwnedUserId, (OwnedEventId, Receipt)>>>>,
@@ -116,6 +121,8 @@ impl MemoryStore {
stripped_room_infos: Default::default(),
stripped_room_state: Default::default(),
stripped_members: Default::default(),
stripped_joined_user_ids: Default::default(),
stripped_invited_user_ids: Default::default(),
presence: Default::default(),
room_user_receipts: Default::default(),
room_event_receipts: Default::default(),
@@ -247,6 +254,39 @@ impl MemoryStore {
for (room, events) in &changes.stripped_members {
for event in events.values() {
match event.content.membership {
MembershipState::Join => {
self.stripped_joined_user_ids
.entry(room.clone())
.or_insert_with(DashSet::new)
.insert(event.state_key.clone());
self.stripped_invited_user_ids
.entry(room.clone())
.or_insert_with(DashSet::new)
.remove(&event.state_key);
}
MembershipState::Invite => {
self.stripped_invited_user_ids
.entry(room.clone())
.or_insert_with(DashSet::new)
.insert(event.state_key.clone());
self.stripped_joined_user_ids
.entry(room.clone())
.or_insert_with(DashSet::new)
.remove(&event.state_key);
}
_ => {
self.stripped_joined_user_ids
.entry(room.clone())
.or_insert_with(DashSet::new)
.remove(&event.state_key);
self.stripped_invited_user_ids
.entry(room.clone())
.or_insert_with(DashSet::new)
.remove(&event.state_key);
}
}
self.stripped_members
.entry(room.clone())
.or_default()
@@ -467,15 +507,28 @@ impl MemoryStore {
&self,
room_id: &RoomId,
state_key: &UserId,
) -> Result<Option<OriginalSyncRoomMemberEvent>> {
Ok(self.members.get(room_id).and_then(|m| m.get(state_key).map(|m| m.clone())))
) -> Result<Option<MemberEvent>> {
if let Some(e) = self.members.get(room_id).and_then(|m| m.get(state_key).map(|m| m.clone()))
{
Ok(Some(e.into()))
} else if let Some(e) =
self.stripped_members.get(room_id).and_then(|m| m.get(state_key).map(|m| m.clone()))
{
Ok(Some(e.into()))
} else {
Ok(None)
}
}
fn get_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
self.members
.get(room_id)
.map(|u| u.iter().map(|u| u.key().clone()).collect())
.unwrap_or_default()
if let Some(u) = self.members.get(room_id) {
u.iter().map(|u| u.key().clone()).collect()
} else {
self.stripped_members
.get(room_id)
.map(|u| u.iter().map(|u| u.key().clone()).collect())
.unwrap_or_default()
}
}
fn get_invited_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
@@ -492,6 +545,20 @@ impl MemoryStore {
.unwrap_or_default()
}
fn get_stripped_invited_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
self.stripped_invited_user_ids
.get(room_id)
.map(|u| u.iter().map(|u| u.clone()).collect())
.unwrap_or_default()
}
fn get_stripped_joined_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
self.stripped_joined_user_ids
.get(room_id)
.map(|u| u.iter().map(|u| u.clone()).collect())
.unwrap_or_default()
}
fn get_room_infos(&self) -> Vec<RoomInfo> {
self.room_info.iter().map(|r| r.clone()).collect()
}
@@ -686,7 +753,7 @@ impl StateStore for MemoryStore {
&self,
room_id: &RoomId,
state_key: &UserId,
) -> Result<Option<OriginalSyncRoomMemberEvent>> {
) -> Result<Option<MemberEvent>> {
self.get_member_event(room_id, state_key).await
}
@@ -695,11 +762,19 @@ impl StateStore for MemoryStore {
}
async fn get_invited_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
Ok(self.get_invited_user_ids(room_id))
let v = self.get_invited_user_ids(room_id);
if !v.is_empty() {
return Ok(v);
}
Ok(self.get_stripped_invited_user_ids(room_id))
}
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
Ok(self.get_joined_user_ids(room_id))
let v = self.get_joined_user_ids(room_id);
if !v.is_empty() {
return Ok(v);
}
Ok(self.get_stripped_joined_user_ids(room_id))
}
async fn get_room_infos(&self) -> Result<Vec<RoomInfo>> {

View File

@@ -59,6 +59,7 @@ pub type BoxStream<T> = Pin<Box<dyn futures_util::Stream<Item = T> + Send>>;
#[cfg(feature = "experimental-timeline")]
use crate::deserialized_responses::{SyncRoomEvent, TimelineSlice};
use crate::{
deserialized_responses::MemberEvent,
media::MediaRequest,
rooms::{RoomInfo, RoomType},
Room, Session,
@@ -179,7 +180,7 @@ pub trait StateStore: AsyncTraitDeps {
user_id: &UserId,
) -> Result<Option<RoomMemberEventContent>>;
/// Get a raw `MemberEvent` for the given state key in the given room id.
/// Get the `MemberEvent` for the given state key in the given room id.
///
/// # Arguments
///
@@ -190,17 +191,18 @@ pub trait StateStore: AsyncTraitDeps {
&self,
room_id: &RoomId,
state_key: &UserId,
) -> Result<Option<OriginalSyncRoomMemberEvent>>;
) -> Result<Option<MemberEvent>>;
/// Get all the user ids of members for a given room.
/// Get all the user ids of members for a given room, for stripped and
/// regular rooms alike.
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>>;
/// Get all the user ids of members that are in the invited state for a
/// given room.
/// given room, for stripped and regular rooms alike.
async fn get_invited_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>>;
/// Get all the user ids of members that are in the joined state for a
/// given room.
/// given room, for stripped and regular rooms alike.
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>>;
/// Get all the pure `RoomInfo`s the store knows about.

View File

@@ -8,9 +8,15 @@ use ruma::{
State, ToDevice, UnreadNotificationsCount as RumaUnreadNotificationsCount,
},
},
events::{room::member::OriginalRoomMemberEvent, AnyRoomEvent, AnySyncRoomEvent},
events::{
room::member::{
OriginalRoomMemberEvent, OriginalSyncRoomMemberEvent, RoomMemberEventContent,
StrippedRoomMemberEvent,
},
AnyRoomEvent, AnySyncRoomEvent,
},
serde::Raw,
DeviceKeyAlgorithm, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedUserId,
DeviceKeyAlgorithm, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedUserId, UserId,
};
use serde::{Deserialize, Serialize};
@@ -290,6 +296,43 @@ impl TimelineSlice {
}
}
/// Wrapper around both MemberEvent-Types
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum MemberEvent {
Stripped(StrippedRoomMemberEvent),
Original(OriginalSyncRoomMemberEvent),
}
impl MemberEvent {
/// The inner Content of the wrapped Event
pub fn content(&self) -> &RoomMemberEventContent {
match &*self {
MemberEvent::Stripped(e) => &e.content,
MemberEvent::Original(e) => &e.content,
}
}
/// The user id associated to this member event
pub fn user_id(&self) -> &UserId {
match &*self {
MemberEvent::Stripped(e) => &e.state_key,
MemberEvent::Original(e) => &e.state_key,
}
}
}
impl From<StrippedRoomMemberEvent> for MemberEvent {
fn from(other: StrippedRoomMemberEvent) -> Self {
MemberEvent::Stripped(other)
}
}
impl From<OriginalSyncRoomMemberEvent> for MemberEvent {
fn from(other: OriginalSyncRoomMemberEvent) -> Self {
MemberEvent::Original(other)
}
}
/// A deserialized response for the rooms members API call.
///
/// [GET /_matrix/client/r0/rooms/{roomId}/members](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-members)

View File

@@ -19,19 +19,20 @@ use async_trait::async_trait;
#[cfg(feature = "experimental-timeline")]
use futures_util::stream;
use indexed_db_futures::prelude::*;
#[cfg(feature = "experimental-timeline")]
use matrix_sdk_base::{deserialized_responses::SyncRoomEvent, store::BoxStream};
use matrix_sdk_base::{
deserialized_responses::MemberEvent,
media::{MediaRequest, UniqueKey},
store::{Result as StoreResult, StateChanges, StateStore, StoreError},
RoomInfo,
};
#[cfg(feature = "experimental-timeline")]
use matrix_sdk_base::{deserialized_responses::SyncRoomEvent, store::BoxStream};
use matrix_sdk_store_encryption::{Error as EncryptionError, StoreCipher};
use ruma::{
events::{
presence::PresenceEvent,
receipt::Receipt,
room::member::{MembershipState, OriginalSyncRoomMemberEvent, RoomMemberEventContent},
room::member::{MembershipState, RoomMemberEventContent},
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent,
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
},
@@ -120,6 +121,8 @@ mod KEYS {
pub const STRIPPED_ROOM_INFOS: &str = "stripped_room_infos";
pub const STRIPPED_MEMBERS: &str = "stripped_members";
pub const STRIPPED_ROOM_STATE: &str = "stripped_room_state";
pub const STRIPPED_JOINED_USER_IDS: &str = "stripped_joined_user_ids";
pub const STRIPPED_INVITED_USER_IDS: &str = "stripped_invited_user_ids";
pub const ROOM_USER_RECEIPTS: &str = "room_user_receipts";
pub const ROOM_EVENT_RECEIPTS: &str = "room_event_receipts";
@@ -183,6 +186,8 @@ impl IndexeddbStore {
db.create_object_store(KEYS::STRIPPED_ROOM_INFOS)?;
db.create_object_store(KEYS::STRIPPED_MEMBERS)?;
db.create_object_store(KEYS::STRIPPED_ROOM_STATE)?;
db.create_object_store(KEYS::STRIPPED_JOINED_USER_IDS)?;
db.create_object_store(KEYS::STRIPPED_INVITED_USER_IDS)?;
db.create_object_store(KEYS::ROOM_USER_RECEIPTS)?;
db.create_object_store(KEYS::ROOM_EVENT_RECEIPTS)?;
@@ -369,7 +374,6 @@ impl IndexeddbStore {
(!changes.room_infos.is_empty(), KEYS::ROOM_INFOS),
(!changes.receipts.is_empty(), KEYS::ROOM_EVENT_RECEIPTS),
(!changes.stripped_state.is_empty(), KEYS::STRIPPED_ROOM_STATE),
(!changes.stripped_members.is_empty(), KEYS::STRIPPED_MEMBERS),
(!changes.stripped_room_infos.is_empty(), KEYS::STRIPPED_ROOM_INFOS),
]
.iter()
@@ -385,6 +389,14 @@ impl IndexeddbStore {
])
}
if !changes.stripped_members.is_empty() {
stores.extend([
KEYS::STRIPPED_MEMBERS,
KEYS::STRIPPED_INVITED_USER_IDS,
KEYS::STRIPPED_JOINED_USER_IDS,
])
}
if !changes.receipts.is_empty() {
stores.extend([KEYS::ROOM_EVENT_RECEIPTS, KEYS::ROOM_USER_RECEIPTS])
}
@@ -486,10 +498,38 @@ impl IndexeddbStore {
if !changes.stripped_members.is_empty() {
let store = tx.object_store(KEYS::STRIPPED_MEMBERS)?;
let joined = tx.object_store(KEYS::STRIPPED_JOINED_USER_IDS)?;
let invited = tx.object_store(KEYS::STRIPPED_INVITED_USER_IDS)?;
for (room, events) in &changes.stripped_members {
for event in events.values() {
let key = self.encode_key(KEYS::STRIPPED_MEMBERS, (room, &event.state_key));
store.put_key_val(&key, &self.serialize_event(&event)?)?;
let key = (room, &event.state_key);
match event.content.membership {
MembershipState::Join => {
joined.put_key_val_owned(
&self.encode_key(KEYS::STRIPPED_JOINED_USER_IDS, key),
&self.serialize_event(&event.state_key)?,
)?;
invited
.delete(&self.encode_key(KEYS::STRIPPED_INVITED_USER_IDS, key))?;
}
MembershipState::Invite => {
invited.put_key_val_owned(
&self.encode_key(KEYS::STRIPPED_INVITED_USER_IDS, key),
&self.serialize_event(&event.state_key)?,
)?;
joined.delete(&self.encode_key(KEYS::STRIPPED_JOINED_USER_IDS, key))?;
}
_ => {
joined.delete(&self.encode_key(KEYS::STRIPPED_JOINED_USER_IDS, key))?;
invited
.delete(&self.encode_key(KEYS::STRIPPED_INVITED_USER_IDS, key))?;
}
}
store.put_key_val(
&self.encode_key(KEYS::STRIPPED_MEMBERS, key),
&self.serialize_event(&event)?,
)?;
}
}
}
@@ -508,14 +548,14 @@ impl IndexeddbStore {
}
if !changes.members.is_empty() {
let profiles_store = tx.object_store(KEYS::PROFILES)?;
let joined = tx.object_store(KEYS::JOINED_USER_IDS)?;
let invited = tx.object_store(KEYS::INVITED_USER_IDS)?;
let members = tx.object_store(KEYS::MEMBERS)?;
for (room, events) in &changes.members {
let profile_changes = changes.profiles.get(room);
let profiles_store = tx.object_store(KEYS::PROFILES)?;
let joined = tx.object_store(KEYS::JOINED_USER_IDS)?;
let invited = tx.object_store(KEYS::INVITED_USER_IDS)?;
let members = tx.object_store(KEYS::MEMBERS)?;
for event in events.values() {
let key = (room, &event.state_key);
@@ -862,14 +902,30 @@ impl IndexeddbStore {
&self,
room_id: &RoomId,
state_key: &UserId,
) -> Result<Option<OriginalSyncRoomMemberEvent>> {
self.inner
) -> Result<Option<MemberEvent>> {
if let Some(e) = self
.inner
.transaction_on_one_with_mode(KEYS::MEMBERS, IdbTransactionMode::Readonly)?
.object_store(KEYS::MEMBERS)?
.get(&self.encode_key(KEYS::MEMBERS, (room_id, state_key)))?
.await?
.map(|f| self.deserialize_event(f))
.transpose()
.transpose()?
{
Ok(Some(MemberEvent::Original(e)))
} else if let Some(e) = self
.inner
.transaction_on_one_with_mode(KEYS::STRIPPED_MEMBERS, IdbTransactionMode::Readonly)?
.object_store(KEYS::STRIPPED_MEMBERS)?
.get(&self.encode_key(KEYS::STRIPPED_MEMBERS, (room_id, state_key)))?
.await?
.map(|f| self.deserialize_event(f))
.transpose()?
{
Ok(Some(MemberEvent::Stripped(e)))
} else {
Ok(None)
}
}
pub async fn get_user_ids_stream(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
@@ -905,6 +961,51 @@ impl IndexeddbStore {
.collect::<Vec<_>>())
}
pub async fn get_stripped_user_ids_stream(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
Ok([
self.get_stripped_invited_user_ids(room_id).await?,
self.get_stripped_joined_user_ids(room_id).await?,
]
.concat())
}
pub async fn get_stripped_invited_user_ids(
&self,
room_id: &RoomId,
) -> Result<Vec<OwnedUserId>> {
let range = self.encode_to_range(KEYS::STRIPPED_INVITED_USER_IDS, room_id)?;
let entries = self
.inner
.transaction_on_one_with_mode(
KEYS::STRIPPED_INVITED_USER_IDS,
IdbTransactionMode::Readonly,
)?
.object_store(KEYS::STRIPPED_INVITED_USER_IDS)?
.get_all_with_key(&range)?
.await?
.iter()
.filter_map(|f| self.deserialize_event::<OwnedUserId>(f).ok())
.collect::<Vec<_>>();
Ok(entries)
}
pub async fn get_stripped_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
let range = self.encode_to_range(KEYS::STRIPPED_JOINED_USER_IDS, room_id)?;
Ok(self
.inner
.transaction_on_one_with_mode(
KEYS::STRIPPED_JOINED_USER_IDS,
IdbTransactionMode::Readonly,
)?
.object_store(KEYS::STRIPPED_JOINED_USER_IDS)?
.get_all_with_key(&range)?
.await?
.iter()
.filter_map(|f| self.deserialize_event::<OwnedUserId>(f).ok())
.collect::<Vec<_>>())
}
pub async fn get_room_infos(&self) -> Result<Vec<RoomInfo>> {
let entries: Vec<_> = self
.inner
@@ -1242,20 +1343,32 @@ impl StateStore for IndexeddbStore {
&self,
room_id: &RoomId,
state_key: &UserId,
) -> StoreResult<Option<OriginalSyncRoomMemberEvent>> {
) -> StoreResult<Option<MemberEvent>> {
self.get_member_event(room_id, state_key).await.map_err(|e| e.into())
}
async fn get_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<OwnedUserId>> {
self.get_user_ids_stream(room_id).await.map_err(|e| e.into())
let ids: Vec<OwnedUserId> = self.get_user_ids_stream(room_id).await?;
if !ids.is_empty() {
return Ok(ids);
}
self.get_stripped_user_ids_stream(room_id).await.map_err(|e| e.into())
}
async fn get_invited_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<OwnedUserId>> {
self.get_invited_user_ids(room_id).await.map_err(|e| e.into())
let ids: Vec<OwnedUserId> = self.get_invited_user_ids(room_id).await?;
if !ids.is_empty() {
return Ok(ids);
}
self.get_stripped_invited_user_ids(room_id).await.map_err(|e| e.into())
}
async fn get_joined_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<OwnedUserId>> {
self.get_joined_user_ids(room_id).await.map_err(|e| e.into())
let ids: Vec<OwnedUserId> = self.get_joined_user_ids(room_id).await?;
if !ids.is_empty() {
return Ok(ids);
}
self.get_stripped_joined_user_ids(room_id).await.map_err(|e| e.into())
}
async fn get_room_infos(&self) -> StoreResult<Vec<RoomInfo>> {

View File

@@ -25,19 +25,20 @@ use async_stream::stream;
use async_trait::async_trait;
use futures_core::stream::Stream;
use futures_util::stream::{self, StreamExt, TryStreamExt};
#[cfg(feature = "experimental-timeline")]
use matrix_sdk_base::{deserialized_responses::SyncRoomEvent, store::BoxStream};
use matrix_sdk_base::{
deserialized_responses::MemberEvent,
media::{MediaRequest, UniqueKey},
store::{Result as StoreResult, StateChanges, StateStore, StoreError},
RoomInfo,
};
#[cfg(feature = "experimental-timeline")]
use matrix_sdk_base::{deserialized_responses::SyncRoomEvent, store::BoxStream};
use matrix_sdk_store_encryption::{Error as KeyEncryptionError, StoreCipher};
use ruma::{
events::{
presence::PresenceEvent,
receipt::Receipt,
room::member::{MembershipState, OriginalSyncRoomMemberEvent, RoomMemberEventContent},
room::member::{MembershipState, RoomMemberEventContent},
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent,
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
},
@@ -134,6 +135,8 @@ const ROOM_STATE: &str = "room-state";
const ROOM_USER_RECEIPT: &str = "room-user-receipt";
const ROOM: &str = "room";
const SESSION: &str = "session";
const STRIPPED_INVITED_USER_ID: &str = "stripped-invited-user-id";
const STRIPPED_JOINED_USER_ID: &str = "stripped-joined-user-id";
const STRIPPED_ROOM_INFO: &str = "stripped-room-info";
const STRIPPED_ROOM_MEMBER: &str = "stripped-room-member";
const STRIPPED_ROOM_STATE: &str = "stripped-room-state";
@@ -159,6 +162,8 @@ pub struct SledStore {
room_info: Tree,
room_state: Tree,
room_account_data: Tree,
stripped_joined_user_ids: Tree,
stripped_invited_user_ids: Tree,
stripped_room_infos: Tree,
stripped_room_state: Tree,
stripped_members: Tree,
@@ -205,6 +210,8 @@ impl SledStore {
let presence = db.open_tree(PRESENCE)?;
let room_account_data = db.open_tree(ROOM_ACCOUNT_DATA)?;
let stripped_joined_user_ids = db.open_tree(STRIPPED_JOINED_USER_ID)?;
let stripped_invited_user_ids = db.open_tree(STRIPPED_INVITED_USER_ID)?;
let stripped_room_infos = db.open_tree(STRIPPED_ROOM_INFO)?;
let stripped_members = db.open_tree(STRIPPED_ROOM_MEMBER)?;
let stripped_room_state = db.open_tree(STRIPPED_ROOM_STATE)?;
@@ -238,6 +245,8 @@ impl SledStore {
presence,
room_state,
room_info,
stripped_joined_user_ids,
stripped_invited_user_ids,
stripped_room_infos,
stripped_members,
stripped_room_state,
@@ -374,9 +383,8 @@ impl SledStore {
pub async fn save_changes(&self, changes: &StateChanges) -> Result<()> {
let now = Instant::now();
// room state & memberships
let ret: Result<(), TransactionError<SledStoreError>> = (
&self.session,
&self.account_data,
&self.members,
&self.profiles,
&self.display_names,
@@ -385,15 +393,14 @@ impl SledStore {
&self.room_info,
&self.room_state,
&self.room_account_data,
&self.presence,
&self.stripped_joined_user_ids,
&self.stripped_invited_user_ids,
&self.stripped_room_infos,
&self.stripped_members,
&self.stripped_room_state,
)
.transaction(
|(
session,
account_data,
members,
profiles,
display_names,
@@ -402,15 +409,12 @@ impl SledStore {
rooms,
state,
room_account_data,
presence,
stripped_joined,
stripped_invited,
striped_rooms,
stripped_members,
stripped_state,
)| {
if let Some(s) = &changes.sync_token {
session.insert("sync_token".encode(), s.as_str())?;
}
for (room, events) in &changes.members {
let profile_changes = changes.profiles.get(room);
@@ -466,14 +470,6 @@ impl SledStore {
}
}
for (event_type, event) in &changes.account_data {
account_data.insert(
self.encode_key(ACCOUNT_DATA, event_type),
self.serialize_event(&event)
.map_err(ConflictableTransactionError::Abort)?,
)?;
}
for (room, events) in &changes.room_account_data {
for (event_type, event) in events {
room_account_data.insert(
@@ -504,14 +500,6 @@ impl SledStore {
)?;
}
for (sender, event) in &changes.presence {
presence.insert(
self.encode_key(PRESENCE, sender),
self.serialize_event(&event)
.map_err(ConflictableTransactionError::Abort)?,
)?;
}
for (room_id, info) in &changes.stripped_room_infos {
striped_rooms.insert(
self.encode_key(STRIPPED_ROOM_INFO, room_id),
@@ -522,11 +510,34 @@ impl SledStore {
for (room, events) in &changes.stripped_members {
for event in events.values() {
let key = (room, &event.state_key);
match event.content.membership {
MembershipState::Join => {
stripped_joined.insert(
self.encode_key(STRIPPED_JOINED_USER_ID, &key),
event.state_key.as_str(),
)?;
stripped_invited
.remove(self.encode_key(STRIPPED_INVITED_USER_ID, &key))?;
}
MembershipState::Invite => {
stripped_invited.insert(
self.encode_key(STRIPPED_INVITED_USER_ID, &key),
event.state_key.as_str(),
)?;
stripped_joined
.remove(self.encode_key(STRIPPED_JOINED_USER_ID, &key))?;
}
_ => {
stripped_joined
.remove(self.encode_key(STRIPPED_JOINED_USER_ID, &key))?;
stripped_invited
.remove(self.encode_key(STRIPPED_INVITED_USER_ID, &key))?;
}
}
stripped_members.insert(
self.encode_key(
STRIPPED_ROOM_MEMBER,
(room, event.state_key.to_string()),
),
self.encode_key(STRIPPED_ROOM_MEMBER, &key),
self.serialize_event(&event)
.map_err(ConflictableTransactionError::Abort)?,
)?;
@@ -555,8 +566,8 @@ impl SledStore {
ret?;
let ret: Result<(), TransactionError<SledStoreError>> =
(&self.room_user_receipts, &self.room_event_receipts).transaction(
|(room_user_receipts, room_event_receipts)| {
(&self.room_user_receipts, &self.room_event_receipts, &self.presence).transaction(
|(room_user_receipts, room_event_receipts, presence)| {
for (room, content) in &changes.receipts {
for (event_id, receipts) in &content.0 {
for (receipt_type, receipts) in receipts {
@@ -594,6 +605,14 @@ impl SledStore {
}
}
for (sender, event) in &changes.presence {
presence.insert(
self.encode_key(PRESENCE, sender),
self.serialize_event(&event)
.map_err(ConflictableTransactionError::Abort)?,
)?;
}
Ok(())
},
);
@@ -603,6 +622,26 @@ impl SledStore {
#[cfg(feature = "experimental-timeline")]
self.save_room_timeline(changes).await?;
// user state
let ret: Result<(), TransactionError<SledStoreError>> = (&self.session, &self.account_data)
.transaction(|(session, account_data)| {
if let Some(s) = &changes.sync_token {
session.insert("sync_token".encode(), s.as_str())?;
}
for (event_type, event) in &changes.account_data {
account_data.insert(
self.encode_key(ACCOUNT_DATA, event_type),
self.serialize_event(&event)
.map_err(ConflictableTransactionError::Abort)?,
)?;
}
Ok(())
});
ret?;
self.inner.flush_async().await?;
tracing::info!("Saved changes in {:?}", now.elapsed());
@@ -662,11 +701,25 @@ impl SledStore {
&self,
room_id: &RoomId,
state_key: &UserId,
) -> Result<Option<OriginalSyncRoomMemberEvent>> {
) -> Result<Option<MemberEvent>> {
let db = self.clone();
let key = self.encode_key(MEMBER, (room_id, state_key));
spawn_blocking(move || db.members.get(key)?.map(|v| db.deserialize_event(&v)).transpose())
.await?
let stripped_key = self.encode_key(STRIPPED_ROOM_MEMBER, (room_id, state_key));
spawn_blocking(move || {
if let Some(e) = db.members.get(key)?.map(|v| db.deserialize_event(&v)).transpose()? {
Ok(Some(MemberEvent::Original(e)))
} else if let Some(e) = db
.stripped_members
.get(stripped_key)?
.map(|v| db.deserialize_event(&v))
.transpose()?
{
Ok(Some(MemberEvent::Stripped(e)))
} else {
Ok(None)
}
})
.await?
}
pub async fn get_user_ids_stream(
@@ -678,6 +731,15 @@ impl SledStore {
.await?
.chain(self.get_invited_user_ids(room_id).await?))
}
pub async fn get_stripped_user_ids_stream(
&self,
room_id: &RoomId,
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
Ok(self
.get_stripped_joined_user_ids(room_id)
.await?
.chain(self.get_stripped_invited_user_ids(room_id).await?))
}
pub async fn get_invited_user_ids(
&self,
@@ -715,6 +777,42 @@ impl SledStore {
.map_err(|e| StoreError::Backend(anyhow!(e)))
}
pub async fn get_stripped_invited_user_ids(
&self,
room_id: &RoomId,
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
let db = self.clone();
let key = self.encode_key(STRIPPED_INVITED_USER_ID, room_id);
spawn_blocking(move || {
stream::iter(db.stripped_invited_user_ids.scan_prefix(key).map(|u| {
UserId::parse(String::from_utf8_lossy(
&u.map_err(|e| StoreError::Backend(anyhow!(e)))?.1,
))
.map_err(StoreError::Identifier)
}))
})
.await
.map_err(|e| StoreError::Backend(anyhow!(e)))
}
pub async fn get_stripped_joined_user_ids(
&self,
room_id: &RoomId,
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
let db = self.clone();
let key = self.encode_key(STRIPPED_JOINED_USER_ID, room_id);
spawn_blocking(move || {
stream::iter(db.stripped_joined_user_ids.scan_prefix(key).map(|u| {
UserId::parse(String::from_utf8_lossy(
&u.map_err(|e| StoreError::Backend(anyhow!(e)))?.1,
))
.map_err(StoreError::Identifier)
}))
})
.await
.map_err(|e| StoreError::Backend(anyhow!(e)))
}
pub async fn get_room_infos(&self) -> Result<impl Stream<Item = Result<RoomInfo>>> {
let db = self.clone();
spawn_blocking(move || {
@@ -1303,20 +1401,32 @@ impl StateStore for SledStore {
&self,
room_id: &RoomId,
state_key: &UserId,
) -> StoreResult<Option<OriginalSyncRoomMemberEvent>> {
) -> StoreResult<Option<MemberEvent>> {
self.get_member_event(room_id, state_key).await.map_err(Into::into)
}
async fn get_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<OwnedUserId>> {
self.get_user_ids_stream(room_id).await?.try_collect().await
let v: Vec<OwnedUserId> = self.get_user_ids_stream(room_id).await?.try_collect().await?;
if !v.is_empty() {
return Ok(v);
}
self.get_stripped_user_ids_stream(room_id).await?.try_collect().await
}
async fn get_invited_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<OwnedUserId>> {
self.get_invited_user_ids(room_id).await?.try_collect().await
let v: Vec<OwnedUserId> = self.get_invited_user_ids(room_id).await?.try_collect().await?;
if !v.is_empty() {
return Ok(v);
}
self.get_stripped_invited_user_ids(room_id).await?.try_collect().await
}
async fn get_joined_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<OwnedUserId>> {
self.get_joined_user_ids(room_id).await?.try_collect().await
let v: Vec<OwnedUserId> = self.get_joined_user_ids(room_id).await?.try_collect().await?;
if !v.is_empty() {
return Ok(v);
}
self.get_stripped_joined_user_ids(room_id).await?.try_collect().await
}
async fn get_room_infos(&self) -> StoreResult<Vec<RoomInfo>> {

View File

@@ -2249,7 +2249,10 @@ pub(crate) mod tests {
use std::{collections::BTreeMap, convert::TryInto, io::Cursor, str::FromStr, time::Duration};
use matrix_sdk_base::media::{MediaFormat, MediaRequest, MediaThumbnailSize};
use matrix_sdk_base::{
media::{MediaFormat, MediaRequest, MediaThumbnailSize},
DisplayName,
};
#[cfg(feature = "experimental-timeline")]
use matrix_sdk_common::deserialized_responses::SyncRoomEvent;
use matrix_sdk_test::{test_json, EventBuilder, EventsJson};
@@ -3366,7 +3369,10 @@ pub(crate) mod tests {
let _response = client.sync_once(sync_settings).await.unwrap();
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
assert_eq!("example2", room.display_name().await.unwrap());
assert_eq!(
DisplayName::Calculated("example2".to_owned()),
room.display_name().await.unwrap()
);
}
#[async_test]
@@ -3444,7 +3450,7 @@ pub(crate) mod tests {
assert_eq!(client.rooms().len(), 1);
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
assert_eq!("tutorial".to_owned(), room.display_name().await.unwrap());
assert_eq!(DisplayName::Aliased("tutorial".to_owned()), room.display_name().await.unwrap());
let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
.with_status(200)
@@ -3458,7 +3464,10 @@ pub(crate) mod tests {
assert_eq!(client.rooms().len(), 1);
let invited_room = client.get_invited_room(room_id!("!696r7674:example.com")).unwrap();
assert_eq!("My Room Name".to_owned(), invited_room.display_name().await.unwrap());
assert_eq!(
DisplayName::Named("My Room Name".to_owned()),
invited_room.display_name().await.unwrap()
);
}
#[async_test]