From 5b17e57c4ea65a8ac260dc19389e4fbc05b2fc90 Mon Sep 17 00:00:00 2001 From: Devin R Date: Wed, 29 Apr 2020 07:58:31 -0400 Subject: [PATCH 01/12] room/messages: add message de/ser to Room --- matrix_sdk/src/models/message.rs | 233 +++++++++++++++++++++++++++ matrix_sdk/src/models/mod.rs | 1 + matrix_sdk/src/models/room.rs | 52 ++++-- matrix_sdk/src/models/room_member.rs | 1 + matrix_sdk/src/state/mod.rs | 1 + 5 files changed, 274 insertions(+), 14 deletions(-) create mode 100644 matrix_sdk/src/models/message.rs diff --git a/matrix_sdk/src/models/message.rs b/matrix_sdk/src/models/message.rs new file mode 100644 index 00000000..6cb2482f --- /dev/null +++ b/matrix_sdk/src/models/message.rs @@ -0,0 +1,233 @@ +use std::collections::{vec_deque::IntoIter, VecDeque}; + +use crate::events::collections::all::RoomEvent; +use crate::events::room::message::MessageEvent; +use crate::events::EventJson; + +use serde::{de, ser}; + +pub(crate) mod ser_deser { + use super::*; + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let messages: VecDeque> = de::Deserialize::deserialize(deserializer)?; + + // TODO this should probably bail out if deserialization fails not skip + let msgs: VecDeque = messages + .into_iter() + .flat_map(|json| json.deserialize()) + .collect(); + + Ok(MessageQueue { msgs }) + } + + pub fn serialize(msgs: &MessageQueue, serializer: S) -> Result + where + S: ser::Serializer, + { + use ser::Serialize; + + msgs.msgs.serialize(serializer) + } +} + +/// A queue that holds at most 10 messages received from the server. +#[derive(Clone, Debug, Default)] +pub struct MessageQueue { + msgs: VecDeque, +} + +impl PartialEq for MessageQueue { + fn eq(&self, other: &MessageQueue) -> bool { + self.msgs.len() == other.msgs.len() + && self + .msgs + .iter() + .zip(other.msgs.iter()) + .all(|(a, b)| match (a, b) { + (RoomEvent::RoomMessage(msg_a), RoomEvent::RoomMessage(msg_b)) => { + msg_a.event_id == msg_b.event_id + } + _ => false, + }) + } +} + +impl MessageQueue { + /// Create a new empty `MessageQueue`. + pub fn new() -> Self { + Self { + msgs: VecDeque::with_capacity(20), + } + } + + /// Appends a `MessageEvent` to the end of the `MessageQueue`. + /// + /// Removes the oldest element in the queue if there are more than 10 elements. + pub fn push(&mut self, msg: MessageEvent) -> bool { + self.msgs.push_back(RoomEvent::RoomMessage(msg)); + if self.msgs.len() > 10 { + self.msgs.pop_front(); + } + true + } + + pub fn iter(&self) -> impl Iterator { + self.msgs.iter() + } +} + +impl IntoIterator for MessageQueue { + type Item = RoomEvent; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.msgs.into_iter() + } +} + +#[cfg(test)] +mod test { + use super::*; + + use std::collections::HashMap; + use std::convert::TryFrom; + + use crate::events::{collections::all::RoomEvent, EventJson}; + use crate::identifiers::{RoomId, UserId}; + use crate::{state::ClientState, Room}; + + #[test] + fn serialize() { + let id = RoomId::try_from("!roomid:example.com").unwrap(); + let user = UserId::try_from("@example:example.com").unwrap(); + + let mut room = Room::new(&id, &user); + + let json = std::fs::read_to_string("../test_data/events/message_text.json").unwrap(); + let event = serde_json::from_str::>(&json).unwrap(); + + let mut msgs = MessageQueue::new(); + if let Ok(ev) = event.deserialize() { + if let RoomEvent::RoomMessage(msg) = ev { + msgs.push(msg); + } + } + room.messages = msgs; + + let mut joined_rooms = HashMap::new(); + joined_rooms.insert(id, room); + + assert_eq!( + r#"{ + "!roomid:example.com": { + "room_id": "!roomid:example.com", + "room_name": { + "name": null, + "canonical_alias": null, + "aliases": [], + "heroes": [], + "joined_member_count": null, + "invited_member_count": null + }, + "own_user_id": "@example:example.com", + "creator": null, + "members": {}, + "messages": [ + { + "type": "m.room.message", + "content": { + "body": "is dancing", + "format": "org.matrix.custom.html", + "formatted_body": "is dancing", + "msgtype": "m.text" + }, + "event_id": "$152037280074GZeOm:localhost", + "origin_server_ts": 1520372800469, + "sender": "@example:localhost", + "unsigned": { + "age": 598971425 + } + } + ], + "typing_users": [], + "power_levels": null, + "encrypted": false, + "unread_highlight": null, + "unread_notifications": null, + "tombstone": null + } +}"#, + serde_json::to_string_pretty(&joined_rooms).unwrap() + ); + } + + #[test] + fn deserialize() { + let id = RoomId::try_from("!roomid:example.com").unwrap(); + let user = UserId::try_from("@example:example.com").unwrap(); + + let mut room = Room::new(&id, &user); + + let json = std::fs::read_to_string("../test_data/events/message_text.json").unwrap(); + let event = serde_json::from_str::>(&json).unwrap(); + + let mut msgs = MessageQueue::new(); + if let Ok(ev) = event.deserialize() { + if let RoomEvent::RoomMessage(msg) = ev { + msgs.push(msg); + } + } + room.messages = msgs; + + let mut joined_rooms = HashMap::new(); + joined_rooms.insert(id, room.clone()); + + let json = r#"{ + "!roomid:example.com": { + "room_id": "!roomid:example.com", + "room_name": { + "name": null, + "canonical_alias": null, + "aliases": [], + "heroes": [], + "joined_member_count": null, + "invited_member_count": null + }, + "own_user_id": "@example:example.com", + "creator": null, + "members": {}, + "messages": [ + { + "type": "m.room.message", + "content": { + "body": "is dancing", + "format": "org.matrix.custom.html", + "formatted_body": "is dancing", + "msgtype": "m.text" + }, + "event_id": "$152037280074GZeOm:localhost", + "origin_server_ts": 1520372800469, + "sender": "@example:localhost", + "unsigned": { + "age": 598971425 + } + } + ], + "typing_users": [], + "power_levels": null, + "encrypted": false, + "unread_highlight": null, + "unread_notifications": null, + "tombstone": null + } +}"#; + assert_eq!( + joined_rooms, + serde_json::from_str::>(json).unwrap() + ); + } +} diff --git a/matrix_sdk/src/models/mod.rs b/matrix_sdk/src/models/mod.rs index 5e461a32..41f4f461 100644 --- a/matrix_sdk/src/models/mod.rs +++ b/matrix_sdk/src/models/mod.rs @@ -1,4 +1,5 @@ mod event_deser; +mod message; mod room; mod room_member; diff --git a/matrix_sdk/src/models/room.rs b/matrix_sdk/src/models/room.rs index 92c808a7..f73d9409 100644 --- a/matrix_sdk/src/models/room.rs +++ b/matrix_sdk/src/models/room.rs @@ -16,6 +16,7 @@ use std::collections::{BTreeMap, HashMap}; use std::convert::TryFrom; +use super::message::MessageQueue; use super::RoomMember; use crate::api::r0::sync::sync_events::RoomSummary; @@ -26,6 +27,7 @@ use crate::events::room::{ canonical_alias::CanonicalAliasEvent, encryption::EncryptionEvent, member::{MemberEvent, MembershipChange}, + message::MessageEvent, name::NameEvent, power_levels::{NotificationPowerLevels, PowerLevelsEvent, PowerLevelsEventContent}, tombstone::TombstoneEvent, @@ -36,6 +38,7 @@ use crate::identifiers::{RoomAliasId, RoomId, UserId}; use crate::js_int::{Int, UInt}; use serde::{Deserialize, Serialize}; #[derive(Debug, Default, PartialEq, Serialize, Deserialize)] +#[cfg_attr(test, derive(Clone))] /// `RoomName` allows the calculation of a text room name. pub struct RoomName { /// The displayed name of the room. @@ -58,6 +61,7 @@ pub struct RoomName { } #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(test, derive(Clone))] pub struct PowerLevels { /// The level required to ban a user. pub ban: Int, @@ -84,6 +88,7 @@ pub struct PowerLevels { } #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(test, derive(Clone))] pub struct Tombstone { /// A server-defined message. body: String, @@ -92,6 +97,7 @@ pub struct Tombstone { } #[derive(Debug, PartialEq, Serialize, Deserialize)] +#[cfg_attr(test, derive(Clone))] /// A Matrix room. pub struct Room { /// The unique id of the room. @@ -104,6 +110,12 @@ pub struct Room { pub creator: Option, /// The map of room members. pub members: HashMap, + /// A queue of messages no longer than MAX_MSGS + /// + /// This is helpful when using a `StateStore` to avoid multiple requests + /// to the server for messages. + #[serde(with = "super::message::ser_deser")] + pub messages: MessageQueue, /// A list of users that are currently typing. pub typing_users: Vec, /// The power level requirements for specific actions in this room @@ -207,6 +219,7 @@ impl Room { own_user_id: own_user_id.clone(), creator: None, members: HashMap::new(), + messages: MessageQueue::new(), typing_users: Vec::new(), power_levels: None, encrypted: false, @@ -320,6 +333,13 @@ impl Room { } } + /// Handle a room.message event and update the `MessageQueue` if necessary. + /// + /// Returns true if `MessageQueue` was added to. + pub fn handle_message(&mut self, event: &MessageEvent) -> bool { + self.messages.push(event.clone()) + } + /// Handle a room.aliases event, updating the room state if necessary. /// /// Returns true if the room name changed, false otherwise. @@ -396,15 +416,16 @@ impl Room { pub fn receive_timeline_event(&mut self, event: &RoomEvent) -> bool { match event { // update to the current members of the room - RoomEvent::RoomMember(m) => self.handle_membership(m), + RoomEvent::RoomMember(member) => self.handle_membership(member), // finds all events related to the name of the room for later use - RoomEvent::RoomName(n) => self.handle_room_name(n), - RoomEvent::RoomCanonicalAlias(ca) => self.handle_canonical(ca), - RoomEvent::RoomAliases(a) => self.handle_room_aliases(a), + RoomEvent::RoomName(name) => self.handle_room_name(name), + RoomEvent::RoomCanonicalAlias(c_alias) => self.handle_canonical(c_alias), + RoomEvent::RoomAliases(alias) => self.handle_room_aliases(alias), // power levels of the room members - RoomEvent::RoomPowerLevels(p) => self.handle_power_level(p), - RoomEvent::RoomTombstone(t) => self.handle_tombstone(t), - RoomEvent::RoomEncryption(e) => self.handle_encryption_event(e), + RoomEvent::RoomPowerLevels(power) => self.handle_power_level(power), + RoomEvent::RoomTombstone(tomb) => self.handle_tombstone(tomb), + RoomEvent::RoomEncryption(encrypt) => self.handle_encryption_event(encrypt), + RoomEvent::RoomMessage(msg) => self.handle_message(msg), _ => false, } } @@ -418,13 +439,16 @@ impl Room { /// * `event` - The event of the room. pub fn receive_state_event(&mut self, event: &StateEvent) -> bool { match event { - StateEvent::RoomMember(m) => self.handle_membership(m), - StateEvent::RoomName(n) => self.handle_room_name(n), - StateEvent::RoomCanonicalAlias(ca) => self.handle_canonical(ca), - StateEvent::RoomAliases(a) => self.handle_room_aliases(a), - StateEvent::RoomPowerLevels(p) => self.handle_power_level(p), - StateEvent::RoomTombstone(t) => self.handle_tombstone(t), - StateEvent::RoomEncryption(e) => self.handle_encryption_event(e), + // update to the current members of the room + StateEvent::RoomMember(member) => self.handle_membership(member), + // finds all events related to the name of the room for later use + StateEvent::RoomName(name) => self.handle_room_name(name), + StateEvent::RoomCanonicalAlias(c_alias) => self.handle_canonical(c_alias), + StateEvent::RoomAliases(alias) => self.handle_room_aliases(alias), + // power levels of the room members + StateEvent::RoomPowerLevels(power) => self.handle_power_level(power), + StateEvent::RoomTombstone(tomb) => self.handle_tombstone(tomb), + StateEvent::RoomEncryption(encrypt) => self.handle_encryption_event(encrypt), _ => false, } } diff --git a/matrix_sdk/src/models/room_member.rs b/matrix_sdk/src/models/room_member.rs index 882d8751..e0270b00 100644 --- a/matrix_sdk/src/models/room_member.rs +++ b/matrix_sdk/src/models/room_member.rs @@ -28,6 +28,7 @@ use serde::{Deserialize, Serialize}; // Notes: if Alice invites Bob into a room we will get an event with the sender as Alice and the state key as Bob. #[derive(Debug, Serialize, Deserialize)] +#[cfg_attr(test, derive(Clone))] /// A Matrix room member. /// pub struct RoomMember { diff --git a/matrix_sdk/src/state/mod.rs b/matrix_sdk/src/state/mod.rs index 3ca6ea7e..78a35807 100644 --- a/matrix_sdk/src/state/mod.rs +++ b/matrix_sdk/src/state/mod.rs @@ -125,6 +125,7 @@ mod test { "own_user_id": "@example:example.com", "creator": null, "members": {}, + "messages": [], "typing_users": [], "power_levels": null, "encrypted": false, From 9386b500a8ff70f401efc51319d5ab8ce852e970 Mon Sep 17 00:00:00 2001 From: Devin R Date: Wed, 29 Apr 2020 19:28:58 -0400 Subject: [PATCH 02/12] message: keep MessageQueue sorted by origin_server_ts --- matrix_sdk/src/models/message.rs | 130 ++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 44 deletions(-) diff --git a/matrix_sdk/src/models/message.rs b/matrix_sdk/src/models/message.rs index 6cb2482f..f867a4cc 100644 --- a/matrix_sdk/src/models/message.rs +++ b/matrix_sdk/src/models/message.rs @@ -1,43 +1,52 @@ -use std::collections::{vec_deque::IntoIter, VecDeque}; +use std::cmp::Ordering; +use std::ops::Deref; +use std::vec::IntoIter; use crate::events::collections::all::RoomEvent; use crate::events::room::message::MessageEvent; use crate::events::EventJson; -use serde::{de, ser}; - -pub(crate) mod ser_deser { - use super::*; - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - let messages: VecDeque> = de::Deserialize::deserialize(deserializer)?; - - // TODO this should probably bail out if deserialization fails not skip - let msgs: VecDeque = messages - .into_iter() - .flat_map(|json| json.deserialize()) - .collect(); - - Ok(MessageQueue { msgs }) - } - - pub fn serialize(msgs: &MessageQueue, serializer: S) -> Result - where - S: ser::Serializer, - { - use ser::Serialize; - - msgs.msgs.serialize(serializer) - } -} +use serde::{de, ser, Serialize}; /// A queue that holds at most 10 messages received from the server. #[derive(Clone, Debug, Default)] pub struct MessageQueue { - msgs: VecDeque, + msgs: Vec, +} + +#[derive(Clone, Debug, Serialize)] +pub struct MessageWrapper(MessageEvent); + +impl Deref for MessageWrapper { + type Target = MessageEvent; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PartialEq for MessageWrapper { + fn eq(&self, other: &MessageWrapper) -> bool { + self.0.event_id == other.0.event_id + && self.0.room_id == other.0.room_id + && self.0.origin_server_ts == other.0.origin_server_ts + && self.0.sender == other.0.sender + && self.0.content == other.0.content + } +} + +impl Eq for MessageWrapper {} + +impl PartialOrd for MessageWrapper { + fn partial_cmp(&self, other: &MessageWrapper) -> Option { + Some(self.0.origin_server_ts.cmp(&other.0.origin_server_ts)) + } +} + +impl Ord for MessageWrapper { + fn cmp(&self, other: &MessageWrapper) -> Ordering { + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } } impl PartialEq for MessageQueue { @@ -47,12 +56,7 @@ impl PartialEq for MessageQueue { .msgs .iter() .zip(other.msgs.iter()) - .all(|(a, b)| match (a, b) { - (RoomEvent::RoomMessage(msg_a), RoomEvent::RoomMessage(msg_b)) => { - msg_a.event_id == msg_b.event_id - } - _ => false, - }) + .all(|(msg_a, msg_b)| msg_a.event_id == msg_b.event_id) } } @@ -60,28 +64,32 @@ impl MessageQueue { /// Create a new empty `MessageQueue`. pub fn new() -> Self { Self { - msgs: VecDeque::with_capacity(20), + msgs: Vec::with_capacity(20), } } - /// Appends a `MessageEvent` to the end of the `MessageQueue`. + /// Inserts a `MessageEvent` into `MessageQueue`, sorted by by `origin_server_ts`. /// /// Removes the oldest element in the queue if there are more than 10 elements. pub fn push(&mut self, msg: MessageEvent) -> bool { - self.msgs.push_back(RoomEvent::RoomMessage(msg)); + let message = MessageWrapper(msg); + match self.msgs.binary_search_by(|m| m.cmp(&message)) { + Ok(pos) => self.msgs.insert(pos, message), + Err(pos) => self.msgs.insert(pos, message), + } if self.msgs.len() > 10 { - self.msgs.pop_front(); + self.msgs.remove(0); } true } - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.msgs.iter() } } impl IntoIterator for MessageQueue { - type Item = RoomEvent; + type Item = MessageWrapper; type IntoIter = IntoIter; fn into_iter(self) -> Self::IntoIter { @@ -89,6 +97,40 @@ impl IntoIterator for MessageQueue { } } +pub(crate) mod ser_deser { + use super::*; + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let messages: Vec> = de::Deserialize::deserialize(deserializer)?; + + // TODO this should probably bail out if deserialization fails not skip + let msgs: Vec = messages + .into_iter() + .flat_map(|json| json.deserialize()) + .flat_map(|ev| { + if let RoomEvent::RoomMessage(msg) = ev { + Some(msg) + } else { + None + } + }) + .map(MessageWrapper) + .collect(); + + Ok(MessageQueue { msgs }) + } + + pub fn serialize(msgs: &MessageQueue, serializer: S) -> Result + where + S: ser::Serializer, + { + msgs.msgs.serialize(serializer) + } +} + #[cfg(test)] mod test { use super::*; @@ -98,7 +140,7 @@ mod test { use crate::events::{collections::all::RoomEvent, EventJson}; use crate::identifiers::{RoomId, UserId}; - use crate::{state::ClientState, Room}; + use crate::Room; #[test] fn serialize() { From 9788233771e015533bbfc29855bd122a520ce633 Mon Sep 17 00:00:00 2001 From: Devin R Date: Thu, 30 Apr 2020 06:28:36 -0400 Subject: [PATCH 03/12] room/message: make docs more clear, deserialize to MessageEvent over RoomEvent --- matrix_sdk/src/models/message.rs | 14 +++----------- matrix_sdk/src/models/room.rs | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/matrix_sdk/src/models/message.rs b/matrix_sdk/src/models/message.rs index f867a4cc..e65de74a 100644 --- a/matrix_sdk/src/models/message.rs +++ b/matrix_sdk/src/models/message.rs @@ -2,13 +2,12 @@ use std::cmp::Ordering; use std::ops::Deref; use std::vec::IntoIter; -use crate::events::collections::all::RoomEvent; use crate::events::room::message::MessageEvent; use crate::events::EventJson; use serde::{de, ser, Serialize}; -/// A queue that holds at most 10 messages received from the server. +/// A queue that holds the 10 most recent messages received from the server. #[derive(Clone, Debug, Default)] pub struct MessageQueue { msgs: Vec, @@ -104,19 +103,12 @@ pub(crate) mod ser_deser { where D: de::Deserializer<'de>, { - let messages: Vec> = de::Deserialize::deserialize(deserializer)?; + let messages: Vec> = de::Deserialize::deserialize(deserializer)?; - // TODO this should probably bail out if deserialization fails not skip + // TODO this should probably bail out if deserialization fails not skip the message let msgs: Vec = messages .into_iter() .flat_map(|json| json.deserialize()) - .flat_map(|ev| { - if let RoomEvent::RoomMessage(msg) = ev { - Some(msg) - } else { - None - } - }) .map(MessageWrapper) .collect(); diff --git a/matrix_sdk/src/models/room.rs b/matrix_sdk/src/models/room.rs index f73d9409..40e9924b 100644 --- a/matrix_sdk/src/models/room.rs +++ b/matrix_sdk/src/models/room.rs @@ -110,7 +110,7 @@ pub struct Room { pub creator: Option, /// The map of room members. pub members: HashMap, - /// A queue of messages no longer than MAX_MSGS + /// A queue of messages, holds no more than 10 of the most recent messages. /// /// This is helpful when using a `StateStore` to avoid multiple requests /// to the server for messages. From 2c4b6919ef42623b8275d8bfeba183b8e352e4c8 Mon Sep 17 00:00:00 2001 From: Devin R Date: Thu, 30 Apr 2020 06:57:25 -0400 Subject: [PATCH 04/12] message: only push message when timestamp is larger than last msg in queue --- matrix_sdk/src/models/message.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/matrix_sdk/src/models/message.rs b/matrix_sdk/src/models/message.rs index e65de74a..9ad81820 100644 --- a/matrix_sdk/src/models/message.rs +++ b/matrix_sdk/src/models/message.rs @@ -71,6 +71,13 @@ impl MessageQueue { /// /// Removes the oldest element in the queue if there are more than 10 elements. pub fn push(&mut self, msg: MessageEvent) -> bool { + // only push new messages into the queue + if let Some(latest) = self.msgs.last() { + if msg.origin_server_ts < latest.origin_server_ts { + return false; + } + } + let message = MessageWrapper(msg); match self.msgs.binary_search_by(|m| m.cmp(&message)) { Ok(pos) => self.msgs.insert(pos, message), From bd2d6b0fac37dbfd3db531918eac14e28123fee0 Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 5 May 2020 07:04:39 -0400 Subject: [PATCH 05/12] message: push message when queue isn't full, don't insert dup --- matrix_sdk/src/models/message.rs | 62 ++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/matrix_sdk/src/models/message.rs b/matrix_sdk/src/models/message.rs index 9ad81820..ec531b8c 100644 --- a/matrix_sdk/src/models/message.rs +++ b/matrix_sdk/src/models/message.rs @@ -30,7 +30,6 @@ impl PartialEq for MessageWrapper { && self.0.room_id == other.0.room_id && self.0.origin_server_ts == other.0.origin_server_ts && self.0.sender == other.0.sender - && self.0.content == other.0.content } } @@ -73,14 +72,18 @@ impl MessageQueue { pub fn push(&mut self, msg: MessageEvent) -> bool { // only push new messages into the queue if let Some(latest) = self.msgs.last() { - if msg.origin_server_ts < latest.origin_server_ts { + if msg.origin_server_ts < latest.origin_server_ts && self.msgs.len() >= 10 { return false; } } let message = MessageWrapper(msg); match self.msgs.binary_search_by(|m| m.cmp(&message)) { - Ok(pos) => self.msgs.insert(pos, message), + Ok(pos) => { + if self.msgs[pos] != message { + self.msgs.insert(pos, message) + } + } Err(pos) => self.msgs.insert(pos, message), } if self.msgs.len() > 10 { @@ -161,7 +164,52 @@ mod test { let mut joined_rooms = HashMap::new(); joined_rooms.insert(id, room); - + println!("{}", serde_json::to_string_pretty(&joined_rooms).unwrap()); + // this is the correct JSON string changes to `ruma-events` have not been released + // that would fix the doubling of fields + // TODO uncomment when fixed + // assert_eq!( + // r#"{ + // "!roomid:example.com": { + // "room_id": "!roomid:example.com", + // "room_name": { + // "name": null, + // "canonical_alias": null, + // "aliases": [], + // "heroes": [], + // "joined_member_count": null, + // "invited_member_count": null + // }, + // "own_user_id": "@example:example.com", + // "creator": null, + // "members": {}, + // "messages": [ + // { + // "type": "m.room.message", + // "content": { + // "body": "is dancing", + // "format": "org.matrix.custom.html", + // "formatted_body": "is dancing", + // "msgtype": "m.text" + // }, + // "event_id": "$152037280074GZeOm:localhost", + // "origin_server_ts": 1520372800469, + // "sender": "@example:localhost", + // "unsigned": { + // "age": 598971425 + // } + // } + // ], + // "typing_users": [], + // "power_levels": null, + // "encrypted": false, + // "unread_highlight": null, + // "unread_notifications": null, + // "tombstone": null + // } + // }"#, + // serde_json::to_string_pretty(&joined_rooms).unwrap() + // ); assert_eq!( r#"{ "!roomid:example.com": { @@ -179,12 +227,12 @@ mod test { "members": {}, "messages": [ { - "type": "m.room.message", "content": { + "msgtype": "m.text", + "msgtype": "m.text", "body": "is dancing", "format": "org.matrix.custom.html", - "formatted_body": "is dancing", - "msgtype": "m.text" + "formatted_body": "is dancing" }, "event_id": "$152037280074GZeOm:localhost", "origin_server_ts": 1520372800469, From d8883a11c13a6e8b6c79a6d42588c5e1ee7b4c58 Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 5 May 2020 08:33:31 -0400 Subject: [PATCH 06/12] async_client: add endpoints for typing and read notice, ban user and forget room --- matrix_sdk/src/async_client.rs | 103 ++++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 7 deletions(-) diff --git a/matrix_sdk/src/async_client.rs b/matrix_sdk/src/async_client.rs index c304b273..f63933bf 100644 --- a/matrix_sdk/src/async_client.rs +++ b/matrix_sdk/src/async_client.rs @@ -37,8 +37,8 @@ use reqwest::header::{HeaderValue, InvalidHeaderValue}; use url::Url; use crate::events::room::message::MessageEventContent; -use crate::events::EventType; -use crate::identifiers::{RoomId, RoomIdOrAliasId, UserId}; +use crate::events::{EventJson, EventType}; +use crate::identifiers::{EventId, RoomId, RoomIdOrAliasId, UserId}; use crate::Endpoint; #[cfg(feature = "encryption")] @@ -214,21 +214,20 @@ impl SyncSettings { #[cfg(feature = "encryption")] use api::r0::keys::{claim_keys, get_keys, upload_keys, KeyAlgorithm}; -use api::r0::membership::join_room_by_id; -use api::r0::membership::join_room_by_id_or_alias; -use api::r0::membership::kick_user; -use api::r0::membership::leave_room; use api::r0::membership::{ + ban_user, forget_room, invite_user::{self, InvitationRecipient}, - Invite3pid, + join_room_by_id, join_room_by_id_or_alias, kick_user, leave_room, Invite3pid, }; use api::r0::message::create_message_event; use api::r0::message::get_message_events; +use api::r0::receipt::create_receipt; use api::r0::room::create_room; use api::r0::session::login; use api::r0::sync::sync_events; #[cfg(feature = "encryption")] use api::r0::to_device::send_event_to_device; +use api::r0::typing::create_typing_event; impl AsyncClient { /// Creates a new client for making HTTP requests to the given homeserver. @@ -447,6 +446,45 @@ impl AsyncClient { self.send(request).await } + /// Forget a room by `RoomId`. + /// + /// Returns a `forget_room::Response`, an empty response. + /// + /// # Arguments + /// + /// * room_id - The `RoomId` of the room to be forget. + pub async fn forget_room_by_id(&self, room_id: &RoomId) -> Result { + let request = forget_room::Request { + room_id: room_id.clone(), + }; + self.send(request).await + } + + /// Ban a user from a room by `RoomId` and `UserId`. + /// + /// Returns a `ban_user::Response`, an empty response. + /// + /// # Arguments + /// + /// * room_id - The `RoomId` of the room to ban the user from. + /// + /// * user_id - The user to ban by `UserId`. + /// + /// * reason - The reason for banning this user. + pub async fn ban_user( + &self, + room_id: RoomId, + user_id: UserId, + reason: Option, + ) -> Result { + let request = ban_user::Request { + reason, + room_id, + user_id, + }; + self.send(request).await + } + /// Kick a user out of the specified room. /// /// Returns a `kick_user::Response`, an empty response. @@ -612,6 +650,57 @@ impl AsyncClient { self.send(req).await } + /// Send a request to notify the room of a user typing. + /// + /// Returns a `create_typing_event::Response`, an empty response. + /// + /// # Arguments + /// + /// * room_id - The `RoomId` the user is typing in. + /// + /// * user_id - The `UserId` of the user that is typing. + /// + /// * typing - Whether the user is typing, if false `timeout` is not needed. + /// + /// * timeout - Length of time in milliseconds to mark user is typing. + pub async fn typing_notice( + &self, + room_id: &RoomId, + user_id: &UserId, + typing: bool, + timeout: Option, + ) -> Result { + let request = create_typing_event::Request { + room_id: room_id.clone(), + user_id: user_id.clone(), + timeout, + typing, + }; + self.send(request).await + } + + /// Send a request to notify the room of a user typing. + /// + /// Returns a `create_receipt::Response`, an empty response. + /// + /// # Arguments + /// + /// * room_id - The `RoomId` the user is typing in. + /// + /// * event_id - The `UserId` of the user that is typing. + pub async fn read_receipt( + &self, + room_id: &RoomId, + event_id: &EventId, + ) -> Result { + let request = create_receipt::Request { + room_id: room_id.clone(), + event_id: event_id.clone(), + receipt_type: create_receipt::ReceiptType::Read, + }; + self.send(request).await + } + /// Synchronize the client's state with the latest state on the server. /// /// If a `StateStore` is provided and this is the initial sync state will From 5ce62c9b6c368bb65d15a2aa1ae7cc77bdd89d4b Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 5 May 2020 13:21:02 -0400 Subject: [PATCH 07/12] async_client: rebase master onto more-endpoints, fix clippy warning --- matrix_sdk/src/async_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix_sdk/src/async_client.rs b/matrix_sdk/src/async_client.rs index f63933bf..2d875ab3 100644 --- a/matrix_sdk/src/async_client.rs +++ b/matrix_sdk/src/async_client.rs @@ -37,7 +37,7 @@ use reqwest::header::{HeaderValue, InvalidHeaderValue}; use url::Url; use crate::events::room::message::MessageEventContent; -use crate::events::{EventJson, EventType}; +use crate::events::EventType; use crate::identifiers::{EventId, RoomId, RoomIdOrAliasId, UserId}; use crate::Endpoint; From c90eb6a9386a12e132b30f2769c243f426c75cbc Mon Sep 17 00:00:00 2001 From: Devin R Date: Wed, 6 May 2020 06:58:42 -0400 Subject: [PATCH 08/12] message: resolve reviews, deserialization can fail, update for ruma removed PartialEq --- matrix_sdk/src/models/message.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/matrix_sdk/src/models/message.rs b/matrix_sdk/src/models/message.rs index ec531b8c..d1526662 100644 --- a/matrix_sdk/src/models/message.rs +++ b/matrix_sdk/src/models/message.rs @@ -27,9 +27,6 @@ impl Deref for MessageWrapper { impl PartialEq for MessageWrapper { fn eq(&self, other: &MessageWrapper) -> bool { self.0.event_id == other.0.event_id - && self.0.room_id == other.0.room_id - && self.0.origin_server_ts == other.0.origin_server_ts - && self.0.sender == other.0.sender } } @@ -113,14 +110,15 @@ pub(crate) mod ser_deser { where D: de::Deserializer<'de>, { + use serde::de::Error; + let messages: Vec> = de::Deserialize::deserialize(deserializer)?; - // TODO this should probably bail out if deserialization fails not skip the message - let msgs: Vec = messages - .into_iter() - .flat_map(|json| json.deserialize()) - .map(MessageWrapper) - .collect(); + let mut msgs = vec![]; + for json in messages { + let msg = json.deserialize().map_err(D::Error::custom)?; + msgs.push(MessageWrapper(msg)); + } Ok(MessageQueue { msgs }) } From 522a8435d0bca3614787e9bd67b0cf852d352d2e Mon Sep 17 00:00:00 2001 From: Devin R Date: Wed, 6 May 2020 08:36:28 -0400 Subject: [PATCH 09/12] async_client: add tests for enpoints, fix docs --- matrix_sdk/src/async_client.rs | 331 ++++++++++++++++++++++++++++++--- 1 file changed, 301 insertions(+), 30 deletions(-) diff --git a/matrix_sdk/src/async_client.rs b/matrix_sdk/src/async_client.rs index 2d875ab3..600a3f3c 100644 --- a/matrix_sdk/src/async_client.rs +++ b/matrix_sdk/src/async_client.rs @@ -345,6 +345,7 @@ impl AsyncClient { /// This allows `AsyncClient` to manually sync state with the provided `StateStore`. /// /// Returns true when a successful `StateStore` sync has completed. + /// /// # Examples /// /// ```no_run @@ -415,8 +416,8 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - The `RoomId` of the room to be joined. - pub async fn join_room_by_id(&mut self, room_id: &RoomId) -> Result { + /// * `room_id` - The `RoomId` of the room to be joined. + pub async fn join_room_by_id(&self, room_id: &RoomId) -> Result { let request = join_room_by_id::Request { room_id: room_id.clone(), third_party_signed: None, @@ -431,7 +432,7 @@ impl AsyncClient { /// /// # Arguments /// - /// * alias - The `RoomId` or `RoomAliasId` of the room to be joined. + /// * `alias` - The `RoomId` or `RoomAliasId` of the room to be joined. /// An alias looks like this `#name:example.com` pub async fn join_room_by_id_or_alias( &self, @@ -452,7 +453,7 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - The `RoomId` of the room to be forget. + /// * `room_id` - The `RoomId` of the room to be forget. pub async fn forget_room_by_id(&self, room_id: &RoomId) -> Result { let request = forget_room::Request { room_id: room_id.clone(), @@ -466,21 +467,21 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - The `RoomId` of the room to ban the user from. + /// * `room_id` - The `RoomId` of the room to ban the user from. /// - /// * user_id - The user to ban by `UserId`. + /// * `user_id` - The user to ban by `UserId`. /// - /// * reason - The reason for banning this user. + /// * `reason` - The reason for banning this user. pub async fn ban_user( &self, - room_id: RoomId, - user_id: UserId, + room_id: &RoomId, + user_id: &UserId, reason: Option, ) -> Result { let request = ban_user::Request { reason, - room_id, - user_id, + room_id: room_id.clone(), + user_id: user_id.clone(), }; self.send(request).await } @@ -491,11 +492,11 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - The `RoomId` of the room the user should be kicked out of. + /// * `room_id` - The `RoomId` of the room the user should be kicked out of. /// - /// * user_id - The `UserId` of the user that should be kicked out of the room. + /// * `user_id` - The `UserId` of the user that should be kicked out of the room. /// - /// * reason - Optional reason why the room member is being kicked out. + /// * `reason` - Optional reason why the room member is being kicked out. pub async fn kick_user( &self, room_id: &RoomId, @@ -516,7 +517,7 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - The `RoomId` of the room to leave. + /// * `room_id` - The `RoomId` of the room to leave. /// pub async fn leave_room(&self, room_id: &RoomId) -> Result { let request = leave_room::Request { @@ -531,9 +532,9 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - The `RoomId` of the room to invite the specified user to. + /// * `room_id` - The `RoomId` of the room to invite the specified user to. /// - /// * user_id - The `UserId` of the user to invite to the room. + /// * `user_id` - The `UserId` of the user to invite to the room. pub async fn invite_user_by_id( &self, room_id: &RoomId, @@ -554,9 +555,9 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - The `RoomId` of the room to invite the specified user to. + /// * `room_id` - The `RoomId` of the room to invite the specified user to. /// - /// * invite_id - A third party id of a user to invite to the room. + /// * `invite_id` - A third party id of a user to invite to the room. pub async fn invite_user_by_3pid( &self, room_id: &RoomId, @@ -576,7 +577,7 @@ impl AsyncClient { /// /// # Arguments /// - /// * room - The easiest way to create this request is using the `RoomBuilder`. + /// * `room` - The easiest way to create this request is using the `RoomBuilder`. /// /// # Examples /// ```no_run @@ -615,7 +616,7 @@ impl AsyncClient { /// /// # Arguments /// - /// * request - The easiest way to create a `Request` is using the + /// * `request` - The easiest way to create a `Request` is using the /// `MessagesRequestBuilder`. /// /// # Examples @@ -656,13 +657,13 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - The `RoomId` the user is typing in. + /// * `room_id` - The `RoomId` the user is typing in. /// - /// * user_id - The `UserId` of the user that is typing. + /// * `user_id` - The `UserId` of the user that is typing. /// - /// * typing - Whether the user is typing, if false `timeout` is not needed. + /// * `typing` - Whether the user is typing, if false `timeout` is not needed. /// - /// * timeout - Length of time in milliseconds to mark user is typing. + /// * `timeout` - Length of time in milliseconds to mark user is typing. pub async fn typing_notice( &self, room_id: &RoomId, @@ -685,9 +686,9 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - The `RoomId` the user is typing in. + /// * `room_id` - The `RoomId` the user is typing in. /// - /// * event_id - The `UserId` of the user that is typing. + /// * `event_id` - The `UserId` of the user that is typing. pub async fn read_receipt( &self, room_id: &RoomId, @@ -1297,13 +1298,17 @@ impl AsyncClient { #[cfg(test)] mod test { - use super::{AsyncClient, Url}; + use super::{ + ban_user, create_receipt, create_typing_event, forget_room, invite_user, kick_user, + leave_room, + }; + use super::{AsyncClient, Session, Url}; use crate::events::collections::all::RoomEvent; - use crate::identifiers::{RoomId, UserId}; + use crate::identifiers::{EventId, RoomId, UserId}; use crate::test_builder::EventBuilder; - use mockito::mock; + use mockito::{mock, Matcher}; use std::convert::TryFrom; use std::str::FromStr; @@ -1407,4 +1412,270 @@ mod test { panic!("this request should return an `Err` variant") } } + + #[tokio::test] + async fn join_room() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + + let session = Session { + access_token: "1234".to_owned(), + user_id: UserId::try_from("@example:localhost").unwrap(), + device_id: "DEVICEID".to_owned(), + }; + + let _m = mock( + "POST", + Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/join".to_string()), + ) + .with_status(200) + .with_body_from_file("../test_data/room_id.json") + .create(); + + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let room_id = RoomId::try_from("!testroom:example.org").unwrap(); + + assert_eq!( + // this is the `join_by_room_id::Response` but since no PartialEq we check the RoomId field + client.join_room_by_id(&room_id).await.unwrap().room_id, + room_id + ); + } + + #[tokio::test] + #[allow(irrefutable_let_patterns)] + async fn invite_room() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + let user = UserId::try_from("@example:localhost").unwrap(); + let room_id = RoomId::try_from("!testroom:example.org").unwrap(); + + let session = Session { + access_token: "1234".to_owned(), + user_id: user.clone(), + device_id: "DEVICEID".to_owned(), + }; + + let _m = mock( + "POST", + Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/invite".to_string()), + ) + .with_status(200) + .with_body_from_file("../test_data/logout_response.json") + .create(); + + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + + if let invite_user::Response = client.invite_user_by_id(&room_id, &user).await.unwrap() {} + } + + #[tokio::test] + #[allow(irrefutable_let_patterns)] + async fn leave_room() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + + let session = Session { + access_token: "1234".to_owned(), + user_id: UserId::try_from("@example:localhost").unwrap(), + device_id: "DEVICEID".to_owned(), + }; + + let _m = mock( + "POST", + Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/leave".to_string()), + ) + .with_status(200) + // this is an empty JSON object + .with_body_from_file("../test_data/logout_response.json") + .create(); + + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let room_id = RoomId::try_from("!testroom:example.org").unwrap(); + + let response = client.leave_room(&room_id).await.unwrap(); + if let leave_room::Response = response { + } else { + panic!( + "expected `ruma_client_api::leave_room::Response` found {:?}", + response + ) + } + } + + #[tokio::test] + #[allow(irrefutable_let_patterns)] + async fn ban_user() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + let user = UserId::try_from("@example:localhost").unwrap(); + let room_id = RoomId::try_from("!testroom:example.org").unwrap(); + + let session = Session { + access_token: "1234".to_owned(), + user_id: user.clone(), + device_id: "DEVICEID".to_owned(), + }; + + let _m = mock( + "POST", + Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/ban".to_string()), + ) + .with_status(200) + // this is an empty JSON object + .with_body_from_file("../test_data/logout_response.json") + .create(); + + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + + let response = client.ban_user(&room_id, &user, None).await.unwrap(); + if let ban_user::Response = response { + } else { + panic!( + "expected `ruma_client_api::ban_user::Response` found {:?}", + response + ) + } + } + + #[tokio::test] + #[allow(irrefutable_let_patterns)] + async fn kick_user() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + let user = UserId::try_from("@example:localhost").unwrap(); + let room_id = RoomId::try_from("!testroom:example.org").unwrap(); + + let session = Session { + access_token: "1234".to_owned(), + user_id: user.clone(), + device_id: "DEVICEID".to_owned(), + }; + + let _m = mock( + "POST", + Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/kick".to_string()), + ) + .with_status(200) + // this is an empty JSON object + .with_body_from_file("../test_data/logout_response.json") + .create(); + + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + + let response = client.kick_user(&room_id, &user, None).await.unwrap(); + if let kick_user::Response = response { + } else { + panic!( + "expected `ruma_client_api::kick_user::Response` found {:?}", + response + ) + } + } + + #[tokio::test] + #[allow(irrefutable_let_patterns)] + async fn forget_room() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + let user = UserId::try_from("@example:localhost").unwrap(); + let room_id = RoomId::try_from("!testroom:example.org").unwrap(); + + let session = Session { + access_token: "1234".to_owned(), + user_id: user.clone(), + device_id: "DEVICEID".to_owned(), + }; + + let _m = mock( + "POST", + Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/forget".to_string()), + ) + .with_status(200) + // this is an empty JSON object + .with_body_from_file("../test_data/logout_response.json") + .create(); + + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + + let response = client.forget_room_by_id(&room_id).await.unwrap(); + if let forget_room::Response = response { + } else { + panic!( + "expected `ruma_client_api::forget_room::Response` found {:?}", + response + ) + } + } + + #[tokio::test] + #[allow(irrefutable_let_patterns)] + async fn read_receipt() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + let user_id = UserId::try_from("@example:localhost").unwrap(); + let room_id = RoomId::try_from("!testroom:example.org").unwrap(); + let event_id = EventId::new("example.org").unwrap(); + + let session = Session { + access_token: "1234".to_owned(), + user_id, + device_id: "DEVICEID".to_owned(), + }; + + let _m = mock( + "POST", + Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/receipt".to_string()), + ) + .with_status(200) + // this is an empty JSON object + .with_body_from_file("../test_data/logout_response.json") + .create(); + + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + + let response = client.read_receipt(&room_id, &event_id).await.unwrap(); + if let create_receipt::Response = response { + } else { + panic!( + "expected `ruma_client_api::create_receipt::Response` found {:?}", + response + ) + } + } + + #[tokio::test] + #[allow(irrefutable_let_patterns)] + async fn typing_notice() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + let user = UserId::try_from("@example:localhost").unwrap(); + let room_id = RoomId::try_from("!testroom:example.org").unwrap(); + + let session = Session { + access_token: "1234".to_owned(), + user_id: user.clone(), + device_id: "DEVICEID".to_owned(), + }; + + let _m = mock( + "PUT", + Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/typing".to_string()), + ) + .with_status(200) + // this is an empty JSON object + .with_body_from_file("../test_data/logout_response.json") + .create(); + + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + + let response = client + .typing_notice( + &room_id, + &user, + true, + Some(std::time::Duration::from_secs(1)), + ) + .await + .unwrap(); + if let create_typing_event::Response = response { + } else { + panic!( + "expected `ruma_client_api::create_typing_event::Response` found {:?}", + response + ) + } + } } From 9939efe0e84b16b44ee46ae8e3867c22f88fff06 Mon Sep 17 00:00:00 2001 From: Devin R Date: Wed, 6 May 2020 21:17:21 -0400 Subject: [PATCH 10/12] message: feature flag message queue storage in Room struct --- matrix_sdk/Cargo.toml | 1 + matrix_sdk/src/models/message.rs | 5 +++++ matrix_sdk/src/models/mod.rs | 2 ++ matrix_sdk/src/models/room.rs | 12 +++++++++++- matrix_sdk/src/state/mod.rs | 29 +++++++++++++++++++++++++++++ 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/matrix_sdk/Cargo.toml b/matrix_sdk/Cargo.toml index a21cde7c..92ea298b 100644 --- a/matrix_sdk/Cargo.toml +++ b/matrix_sdk/Cargo.toml @@ -12,6 +12,7 @@ version = "0.1.0" [features] default = ["encryption", "sqlite-cryptostore"] +messages = [] encryption = ["matrix-sdk-crypto"] sqlite-cryptostore = ["matrix-sdk-crypto/sqlite-cryptostore"] diff --git a/matrix_sdk/src/models/message.rs b/matrix_sdk/src/models/message.rs index d1526662..2aed0ec5 100644 --- a/matrix_sdk/src/models/message.rs +++ b/matrix_sdk/src/models/message.rs @@ -1,3 +1,8 @@ +//! A queue that holds at most ten of the most recent messages. +//! +//! The `Room` struct optionally holds a `MessageQueue` if the "messages" +//! feature is enabled. + use std::cmp::Ordering; use std::ops::Deref; use std::vec::IntoIter; diff --git a/matrix_sdk/src/models/mod.rs b/matrix_sdk/src/models/mod.rs index 41f4f461..6604d61d 100644 --- a/matrix_sdk/src/models/mod.rs +++ b/matrix_sdk/src/models/mod.rs @@ -1,4 +1,6 @@ mod event_deser; +#[cfg(feature = "messages")] +#[cfg_attr(docsrs, doc(cfg(feature = "messages")))] mod message; mod room; mod room_member; diff --git a/matrix_sdk/src/models/room.rs b/matrix_sdk/src/models/room.rs index 40e9924b..197030bf 100644 --- a/matrix_sdk/src/models/room.rs +++ b/matrix_sdk/src/models/room.rs @@ -16,6 +16,7 @@ use std::collections::{BTreeMap, HashMap}; use std::convert::TryFrom; +#[cfg(feature = "messages")] use super::message::MessageQueue; use super::RoomMember; @@ -27,12 +28,15 @@ use crate::events::room::{ canonical_alias::CanonicalAliasEvent, encryption::EncryptionEvent, member::{MemberEvent, MembershipChange}, - message::MessageEvent, name::NameEvent, power_levels::{NotificationPowerLevels, PowerLevelsEvent, PowerLevelsEventContent}, tombstone::TombstoneEvent, }; use crate::events::EventType; + +#[cfg(feature = "messages")] +use crate::events::room::message::MessageEvent; + use crate::identifiers::{RoomAliasId, RoomId, UserId}; use crate::js_int::{Int, UInt}; @@ -114,6 +118,8 @@ pub struct Room { /// /// This is helpful when using a `StateStore` to avoid multiple requests /// to the server for messages. + #[cfg(feature = "messages")] + #[cfg_attr(docsrs, doc(cfg(feature = "messages")))] #[serde(with = "super::message::ser_deser")] pub messages: MessageQueue, /// A list of users that are currently typing. @@ -219,6 +225,7 @@ impl Room { own_user_id: own_user_id.clone(), creator: None, members: HashMap::new(), + #[cfg(feature = "messages")] messages: MessageQueue::new(), typing_users: Vec::new(), power_levels: None, @@ -336,6 +343,8 @@ impl Room { /// Handle a room.message event and update the `MessageQueue` if necessary. /// /// Returns true if `MessageQueue` was added to. + #[cfg(feature = "messages")] + #[cfg_attr(docsrs, doc(cfg(feature = "messages")))] pub fn handle_message(&mut self, event: &MessageEvent) -> bool { self.messages.push(event.clone()) } @@ -425,6 +434,7 @@ impl Room { RoomEvent::RoomPowerLevels(power) => self.handle_power_level(power), RoomEvent::RoomTombstone(tomb) => self.handle_tombstone(tomb), RoomEvent::RoomEncryption(encrypt) => self.handle_encryption_event(encrypt), + #[cfg(feature = "messages")] RoomEvent::RoomMessage(msg) => self.handle_message(msg), _ => false, } diff --git a/matrix_sdk/src/state/mod.rs b/matrix_sdk/src/state/mod.rs index 04b45f94..c4befd4d 100644 --- a/matrix_sdk/src/state/mod.rs +++ b/matrix_sdk/src/state/mod.rs @@ -107,6 +107,35 @@ mod test { let mut joined_rooms = HashMap::new(); joined_rooms.insert(id, room); + + #[cfg(not(feature = "messages"))] + assert_eq!( + r#"{ + "!roomid:example.com": { + "room_id": "!roomid:example.com", + "room_name": { + "name": null, + "canonical_alias": null, + "aliases": [], + "heroes": [], + "joined_member_count": null, + "invited_member_count": null + }, + "own_user_id": "@example:example.com", + "creator": null, + "members": {}, + "typing_users": [], + "power_levels": null, + "encrypted": false, + "unread_highlight": null, + "unread_notifications": null, + "tombstone": null + } +}"#, + serde_json::to_string_pretty(&joined_rooms).unwrap() + ); + + #[cfg(feature = "messages")] assert_eq!( r#"{ "!roomid:example.com": { From 6cbdbdcd2f7666152ad2703015f3d9fd018ee91b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 7 May 2020 08:51:59 +0200 Subject: [PATCH 11/12] matrix-sdk: Rename the types subproject to matrix-sdk-common. --- Cargo.toml | 2 +- matrix_sdk/Cargo.toml | 2 +- matrix_sdk/src/lib.rs | 2 +- {matrix_sdk_types => matrix_sdk_common}/Cargo.toml | 4 ++-- {matrix_sdk_types => matrix_sdk_common}/src/lib.rs | 0 matrix_sdk_crypto/Cargo.toml | 2 +- matrix_sdk_crypto/src/device.rs | 10 +++++----- matrix_sdk_crypto/src/machine.rs | 14 +++++++------- matrix_sdk_crypto/src/memory_stores.rs | 4 ++-- matrix_sdk_crypto/src/olm.rs | 8 ++++---- matrix_sdk_crypto/src/store/memorystore.rs | 4 ++-- matrix_sdk_crypto/src/store/mod.rs | 2 +- matrix_sdk_crypto/src/store/sqlite.rs | 8 ++++---- 13 files changed, 31 insertions(+), 31 deletions(-) rename {matrix_sdk_types => matrix_sdk_common}/Cargo.toml (83%) rename {matrix_sdk_types => matrix_sdk_common}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 44af39f9..9bc413a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,5 @@ members = [ "matrix_sdk", "matrix_sdk_crypto", - "matrix_sdk_types", + "matrix_sdk_common", ] diff --git a/matrix_sdk/Cargo.toml b/matrix_sdk/Cargo.toml index 36e08cfa..9cf6b6e3 100644 --- a/matrix_sdk/Cargo.toml +++ b/matrix_sdk/Cargo.toml @@ -26,7 +26,7 @@ serde = "1.0.106" serde_json = "1.0.52" uuid = { version = "0.8.1", features = ["v4"] } -matrix-sdk-types = { path = "../matrix_sdk_types" } +matrix-sdk-common = { path = "../matrix_sdk_common" } matrix-sdk-crypto = { path = "../matrix_sdk_crypto", optional = true } # Misc dependencies diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index 8999e450..a841bfd6 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -27,7 +27,7 @@ #![deny(missing_docs)] pub use crate::{error::Error, error::Result, session::Session}; -pub use matrix_sdk_types::*; +pub use matrix_sdk_common::*; pub use reqwest::header::InvalidHeaderValue; mod async_client; diff --git a/matrix_sdk_types/Cargo.toml b/matrix_sdk_common/Cargo.toml similarity index 83% rename from matrix_sdk_types/Cargo.toml rename to matrix_sdk_common/Cargo.toml index 9f3e192f..4dcfbb16 100644 --- a/matrix_sdk_types/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -1,11 +1,11 @@ [package] authors = ["Damir Jelić DeviceKeys { let user_id = UserId::try_from("@alice:example.org").unwrap(); diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index c3e63c60..2c4dcd48 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -31,8 +31,8 @@ use super::store::memorystore::MemoryStore; use super::store::sqlite::SqliteStore; use super::{device::Device, store::Result as StoreError, CryptoStore}; -use matrix_sdk_types::api; -use matrix_sdk_types::events::{ +use matrix_sdk_common::api; +use matrix_sdk_common::events::{ collections::all::RoomEvent, room::encrypted::{ CiphertextInfo, EncryptedEvent, EncryptedEventContent, MegolmV1AesSha2Content, @@ -45,7 +45,7 @@ use matrix_sdk_types::events::{ }, Algorithm, EventJson, EventType, }; -use matrix_sdk_types::identifiers::{DeviceId, RoomId, UserId}; +use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; use api::r0::keys; use api::r0::{ @@ -1550,7 +1550,7 @@ mod test { static USER_ID: &str = "@bob:example.org"; static DEVICE_ID: &str = "DEVICEID"; - use matrix_sdk_types::js_int::UInt; + use matrix_sdk_common::js_int::UInt; use std::collections::BTreeMap; use std::convert::TryFrom; use std::fs::File; @@ -1564,10 +1564,10 @@ mod test { use crate::machine::{OlmMachine, OneTimeKeys}; use crate::Device; - use matrix_sdk_types::api::r0::{ + use matrix_sdk_common::api::r0::{ keys, to_device::send_event_to_device::Request as ToDeviceRequest, }; - use matrix_sdk_types::events::{ + use matrix_sdk_common::events::{ collections::all::RoomEvent, room::{ encrypted::{EncryptedEvent, EncryptedEventContent}, @@ -1576,7 +1576,7 @@ mod test { to_device::{AnyToDeviceEvent, ToDeviceEncrypted}, EventJson, EventType, UnsignedData, }; - use matrix_sdk_types::identifiers::{DeviceId, EventId, RoomId, UserId}; + use matrix_sdk_common::identifiers::{DeviceId, EventId, RoomId, UserId}; fn alice_id() -> UserId { UserId::try_from("@alice:example.org").unwrap() diff --git a/matrix_sdk_crypto/src/memory_stores.rs b/matrix_sdk_crypto/src/memory_stores.rs index 2dd20511..0735de30 100644 --- a/matrix_sdk_crypto/src/memory_stores.rs +++ b/matrix_sdk_crypto/src/memory_stores.rs @@ -20,7 +20,7 @@ use tokio::sync::Mutex; use super::device::Device; use super::olm::{InboundGroupSession, Session}; -use matrix_sdk_types::identifiers::{DeviceId, RoomId, UserId}; +use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; /// In-memory store for Olm Sessions. #[derive(Debug, Default)] @@ -215,7 +215,7 @@ mod test { use crate::memory_stores::{DeviceStore, GroupSessionStore, SessionStore}; use crate::olm::test::get_account_and_session; use crate::olm::{InboundGroupSession, OutboundGroupSession}; - use matrix_sdk_types::identifiers::RoomId; + use matrix_sdk_common::identifiers::RoomId; #[tokio::test] async fn test_session_store() { diff --git a/matrix_sdk_crypto/src/olm.rs b/matrix_sdk_crypto/src/olm.rs index c6294177..4df9741f 100644 --- a/matrix_sdk_crypto/src/olm.rs +++ b/matrix_sdk_crypto/src/olm.rs @@ -35,8 +35,8 @@ pub use olm_rs::{ utility::OlmUtility, }; -use matrix_sdk_types::api::r0::keys::SignedKey; -use matrix_sdk_types::identifiers::RoomId; +use matrix_sdk_common::api::r0::keys::SignedKey; +use matrix_sdk_common::identifiers::RoomId; /// Account holding identity keys for which sessions can be created. /// @@ -627,8 +627,8 @@ impl std::fmt::Debug for OutboundGroupSession { #[cfg(test)] pub(crate) mod test { use crate::olm::{Account, InboundGroupSession, OutboundGroupSession, Session}; - use matrix_sdk_types::api::r0::keys::SignedKey; - use matrix_sdk_types::identifiers::RoomId; + use matrix_sdk_common::api::r0::keys::SignedKey; + use matrix_sdk_common::identifiers::RoomId; use olm_rs::session::OlmMessage; use std::collections::BTreeMap; use std::convert::TryFrom; diff --git a/matrix_sdk_crypto/src/store/memorystore.rs b/matrix_sdk_crypto/src/store/memorystore.rs index 3cd422c9..0a921b85 100644 --- a/matrix_sdk_crypto/src/store/memorystore.rs +++ b/matrix_sdk_crypto/src/store/memorystore.rs @@ -21,7 +21,7 @@ use tokio::sync::Mutex; use super::{Account, CryptoStore, InboundGroupSession, Result, Session}; use crate::device::Device; use crate::memory_stores::{DeviceStore, GroupSessionStore, SessionStore, UserDevices}; -use matrix_sdk_types::identifiers::{DeviceId, RoomId, UserId}; +use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; #[derive(Debug)] pub struct MemoryStore { @@ -119,7 +119,7 @@ mod test { use crate::olm::{InboundGroupSession, OutboundGroupSession}; use crate::store::memorystore::MemoryStore; use crate::store::CryptoStore; - use matrix_sdk_types::identifiers::RoomId; + use matrix_sdk_common::identifiers::RoomId; #[tokio::test] async fn test_session_store() { diff --git a/matrix_sdk_crypto/src/store/mod.rs b/matrix_sdk_crypto/src/store/mod.rs index 8d2a7655..36d6c4e4 100644 --- a/matrix_sdk_crypto/src/store/mod.rs +++ b/matrix_sdk_crypto/src/store/mod.rs @@ -26,7 +26,7 @@ use tokio::sync::Mutex; use super::device::Device; use super::memory_stores::UserDevices; use super::olm::{Account, InboundGroupSession, Session}; -use matrix_sdk_types::identifiers::{DeviceId, RoomId, UserId}; +use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; use olm_rs::errors::{OlmAccountError, OlmGroupSessionError, OlmSessionError}; pub mod memorystore; diff --git a/matrix_sdk_crypto/src/store/sqlite.rs b/matrix_sdk_crypto/src/store/sqlite.rs index b7d6fc9a..9d824032 100644 --- a/matrix_sdk_crypto/src/store/sqlite.rs +++ b/matrix_sdk_crypto/src/store/sqlite.rs @@ -30,9 +30,9 @@ use zeroize::Zeroizing; use super::{Account, CryptoStore, CryptoStoreError, InboundGroupSession, Result, Session}; use crate::device::{Device, TrustState}; use crate::memory_stores::{DeviceStore, GroupSessionStore, SessionStore, UserDevices}; -use matrix_sdk_types::api::r0::keys::KeyAlgorithm; -use matrix_sdk_types::events::Algorithm; -use matrix_sdk_types::identifiers::{DeviceId, RoomId, UserId}; +use matrix_sdk_common::api::r0::keys::KeyAlgorithm; +use matrix_sdk_common::events::Algorithm; +use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; /// SQLite based implementation of a `CryptoStore`. pub struct SqliteStore { @@ -694,7 +694,7 @@ impl std::fmt::Debug for SqliteStore { mod test { use crate::device::test::get_device; use crate::olm::GroupSessionKey; - use matrix_sdk_types::api::r0::keys::SignedKey; + use matrix_sdk_common::api::r0::keys::SignedKey; use olm_rs::outbound_group_session::OlmOutboundGroupSession; use std::collections::BTreeMap; use tempfile::tempdir; From a4f5a93880ca67cd7cd25e2348153812851b9f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 7 May 2020 12:51:53 +0200 Subject: [PATCH 12/12] base: Move some test that use the async client out of the base. --- matrix_sdk/src/async_client.rs | 98 +++++++++++++++++++++++++++++++++- matrix_sdk/src/base_client.rs | 43 --------------- matrix_sdk/src/models/mod.rs | 3 -- matrix_sdk/src/models/room.rs | 66 +++++++++++------------ 4 files changed, 127 insertions(+), 83 deletions(-) diff --git a/matrix_sdk/src/async_client.rs b/matrix_sdk/src/async_client.rs index f8800868..6e32f316 100644 --- a/matrix_sdk/src/async_client.rs +++ b/matrix_sdk/src/async_client.rs @@ -1164,8 +1164,9 @@ mod test { ban_user, create_receipt, create_typing_event, forget_room, invite_user, kick_user, leave_room, }; - use super::{AsyncClient, Session, Url}; + use super::{AsyncClient, Session, SyncSettings, Url}; use crate::events::collections::all::RoomEvent; + use crate::events::room::member::MembershipState; use crate::identifiers::{EventId, RoomId, UserId}; use crate::test_builder::EventBuilder; @@ -1173,6 +1174,36 @@ mod test { use mockito::{mock, Matcher}; use std::convert::TryFrom; use std::str::FromStr; + use std::time::Duration; + + #[tokio::test] + async fn account_data() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + + let session = Session { + access_token: "1234".to_owned(), + user_id: UserId::try_from("@example:example.com").unwrap(), + device_id: "DEVICEID".to_owned(), + }; + + let _m = mock( + "GET", + Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()), + ) + .with_status(200) + .with_body_from_file("../test_data/sync.json") + .create(); + + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync(sync_settings).await.unwrap(); + + let bc = &client.base_client; + let ignored_users = bc.ignored_users.read().await; + assert_eq!(1, ignored_users.len()) + } #[tokio::test] async fn client_runner() { @@ -1540,4 +1571,69 @@ mod test { ) } } + + #[tokio::test] + async fn user_presence() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + + let session = Session { + access_token: "1234".to_owned(), + user_id: UserId::try_from("@example:localhost").unwrap(), + device_id: "DEVICEID".to_owned(), + }; + + let _m = mock( + "GET", + Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()), + ) + .with_status(200) + .with_body_from_file("../test_data/sync.json") + .create(); + + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync(sync_settings).await.unwrap(); + + let rooms_lock = &client.base_client.joined_rooms(); + let rooms = rooms_lock.read().await; + let room = &rooms + .get(&RoomId::try_from("!SVkFJHzfwvuaIEawgC:localhost").unwrap()) + .unwrap() + .read() + .await; + + assert_eq!(2, room.members.len()); + for member in room.members.values() { + assert_eq!(MembershipState::Join, member.membership); + } + + assert!(room.power_levels.is_some()) + } + + #[tokio::test] + async fn calculate_room_names_from_summary() { + let homeserver = Url::from_str(&mockito::server_url()).unwrap(); + + let mut bld = EventBuilder::default().build_with_response( + // this sync has no room.name or room.alias events so only relies on summary + "../test_data/sync_with_summary.json", + "GET", + Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()), + ); + + let session = Session { + access_token: "1234".to_owned(), + user_id: UserId::try_from("@example:localhost").unwrap(), + device_id: "DEVICEID".to_owned(), + }; + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let client = bld.set_client(client).to_client().await.unwrap(); + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + let _response = client.sync(sync_settings).await.unwrap(); + + assert_eq!(vec!["example, example2"], client.get_room_names().await); + } } diff --git a/matrix_sdk/src/base_client.rs b/matrix_sdk/src/base_client.rs index 5b9a96bc..3db64e83 100644 --- a/matrix_sdk/src/base_client.rs +++ b/matrix_sdk/src/base_client.rs @@ -918,46 +918,3 @@ impl Client { } } } - -#[cfg(test)] -mod test { - - use crate::identifiers::UserId; - use crate::{AsyncClient, Session, SyncSettings}; - - use mockito::{mock, Matcher}; - use url::Url; - - use std::convert::TryFrom; - use std::str::FromStr; - use std::time::Duration; - - #[tokio::test] - async fn account_data() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - - let session = Session { - access_token: "1234".to_owned(), - user_id: UserId::try_from("@example:example.com").unwrap(), - device_id: "DEVICEID".to_owned(), - }; - - let _m = mock( - "GET", - Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()), - ) - .with_status(200) - .with_body_from_file("../test_data/sync.json") - .create(); - - let client = AsyncClient::new(homeserver, Some(session)).unwrap(); - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync(sync_settings).await.unwrap(); - - let bc = &client.base_client; - let ignored_users = bc.ignored_users.read().await; - assert_eq!(1, ignored_users.len()) - } -} diff --git a/matrix_sdk/src/models/mod.rs b/matrix_sdk/src/models/mod.rs index 6604d61d..218cf28b 100644 --- a/matrix_sdk/src/models/mod.rs +++ b/matrix_sdk/src/models/mod.rs @@ -7,6 +7,3 @@ mod room_member; pub use room::{Room, RoomName}; pub use room_member::RoomMember; - -#[allow(dead_code)] -pub type Token = String; diff --git a/matrix_sdk/src/models/room.rs b/matrix_sdk/src/models/room.rs index 63e11418..638f6385 100644 --- a/matrix_sdk/src/models/room.rs +++ b/matrix_sdk/src/models/room.rs @@ -237,7 +237,7 @@ impl Room { } /// Return the display name of the room. - pub fn calculate_name(&self) -> String { + pub fn display_name(&self) -> String { self.room_name.calculate_name(&self.members) } @@ -490,44 +490,42 @@ impl Room { #[cfg(test)] mod test { use super::*; + use crate::api::r0::sync::sync_events::Response as SyncResponse; use crate::events::room::member::MembershipState; use crate::identifiers::UserId; use crate::test_builder::EventBuilder; - use crate::{AsyncClient, Session, SyncSettings}; + use crate::{Client, Session}; - use mockito::{mock, Matcher}; - use url::Url; + use http::Response; use std::convert::TryFrom; + use std::fs::File; + use std::io::Read; use std::ops::Deref; - use std::str::FromStr; - use std::time::Duration; + + fn sync_response(file: &str) -> SyncResponse { + let mut file = File::open(file).unwrap(); + let mut data = vec![]; + file.read_to_end(&mut data).unwrap(); + let response = Response::builder().body(data).unwrap(); + SyncResponse::try_from(response).unwrap() + } #[tokio::test] async fn user_presence() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let session = Session { access_token: "1234".to_owned(), user_id: UserId::try_from("@example:localhost").unwrap(), device_id: "DEVICEID".to_owned(), }; - let _m = mock( - "GET", - Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()), - ) - .with_status(200) - .with_body_from_file("../test_data/sync.json") - .create(); + let mut response = sync_response("../test_data/sync.json"); - let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let client = Client::new(Some(session)).unwrap(); - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + client.receive_sync_response(&mut response).await.unwrap(); - let _response = client.sync(sync_settings).await.unwrap(); - - let rooms_lock = &client.base_client.joined_rooms(); + let rooms_lock = &client.joined_rooms(); let rooms = rooms_lock.read().await; let room = &rooms .get(&RoomId::try_from("!SVkFJHzfwvuaIEawgC:localhost").unwrap()) @@ -585,7 +583,7 @@ mod test { let room = bld.to_room(); - assert_eq!("tutorial", room.calculate_name()); + assert_eq!("tutorial", room.display_name()); } #[test] @@ -602,7 +600,7 @@ mod test { let room = bld.to_room(); - assert_eq!("tutorial", room.calculate_name()); + assert_eq!("tutorial", room.display_name()); } #[test] @@ -616,31 +614,27 @@ mod test { let room = bld.to_room(); - assert_eq!("room name", room.calculate_name()); + assert_eq!("room name", room.display_name()); } #[tokio::test] async fn calculate_room_names_from_summary() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - - let mut bld = EventBuilder::default().build_with_response( - // this sync has no room.name or room.alias events so only relies on summary - "../test_data/sync_with_summary.json", - "GET", - Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()), - ); + let mut response = sync_response("../test_data/sync_with_summary.json"); let session = Session { access_token: "1234".to_owned(), user_id: UserId::try_from("@example:localhost").unwrap(), device_id: "DEVICEID".to_owned(), }; - let client = AsyncClient::new(homeserver, Some(session)).unwrap(); - let client = bld.set_client(client).to_client().await.unwrap(); + let client = Client::new(Some(session)).unwrap(); + client.receive_sync_response(&mut response).await.unwrap(); - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync(sync_settings).await.unwrap(); + let mut room_names = vec![]; - assert_eq!(vec!["example, example2"], client.get_room_names().await); + for room in client.joined_rooms().read().await.values() { + room_names.push(room.read().await.display_name()) + } + + assert_eq!(vec!["example, example2"], room_names); } }