add test and docs for EventEmitter, fix review issues

master
Devin R 2020-04-02 15:59:13 -04:00
parent 55c25ce6ba
commit 187734f449
6 changed files with 238 additions and 118 deletions

View File

@ -1,14 +1,11 @@
use std::ops::{Deref, DerefMut}; use std::ops::Deref;
use std::sync::{Arc, RwLock}; use std::sync::Arc;
use std::{env, process::exit}; use std::{env, process::exit};
use url::Url; use url::Url;
use matrix_sdk::{ use matrix_sdk::{
self, self,
events::{ events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent},
collections::all::RoomEvent,
room::message::{MessageEvent, MessageEventContent, TextMessageEventContent},
},
AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings, AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings,
}; };
use tokio::sync::Mutex; use tokio::sync::Mutex;

View File

@ -270,7 +270,9 @@ impl AsyncClient {
self.base_client.write().await.event_emitter = Some(emitter); self.base_client.write().await.event_emitter = Some(emitter);
} }
/// Calculates the room name from a `RoomId`, returning a string. /// Returns an `Option` of the room name from a `RoomId`.
///
/// This is a human readable room name.
pub async fn get_room_name(&self, room_id: &str) -> Option<String> { pub async fn get_room_name(&self, room_id: &str) -> Option<String> {
self.base_client self.base_client
.read() .read()
@ -279,21 +281,20 @@ impl AsyncClient {
.await .await
} }
/// Calculates the room names this client knows about. /// Returns a `Vec` of the room names this client knows about.
///
/// This is a human readable list of room names.
pub async fn get_room_names(&self) -> Vec<String> { pub async fn get_room_names(&self) -> Vec<String> {
self.base_client.read().await.calculate_room_names().await self.base_client.read().await.calculate_room_names().await
} }
/// Calculates the room names this client knows about. /// Returns the rooms this client knows about.
///
/// A `HashMap` of room id to `matrix::models::Room`
pub async fn get_rooms(&self) -> HashMap<String, Arc<tokio::sync::Mutex<Room>>> { pub async fn get_rooms(&self) -> HashMap<String, Arc<tokio::sync::Mutex<Room>>> {
self.base_client.read().await.joined_rooms.clone() self.base_client.read().await.joined_rooms.clone()
} }
/// Calculates the room that the client last interacted with.
pub async fn current_room_id(&self) -> Option<RoomId> {
self.base_client.read().await.current_room_id()
}
/// Login to the server. /// Login to the server.
/// ///
/// # Arguments /// # Arguments

View File

@ -35,7 +35,6 @@ use crate::models::Room;
use crate::session::Session; use crate::session::Session;
use crate::EventEmitter; use crate::EventEmitter;
use js_int::UInt;
use tokio::sync::Mutex; use tokio::sync::Mutex;
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
@ -61,33 +60,6 @@ pub struct RoomName {
aliases: Vec<RoomAliasId>, aliases: Vec<RoomAliasId>,
} }
#[derive(Clone, Debug, Default)]
pub struct CurrentRoom {
last_active: Option<UInt>,
current_room_id: Option<RoomId>,
}
impl CurrentRoom {
// TODO when UserId is isomorphic to &str clean this up.
pub(crate) fn comes_after(&self, user: &Uid, event: &PresenceEvent) -> bool {
if user == &event.sender {
if self.last_active.is_none() {
true
} else {
event.content.last_active_ago < self.last_active
}
} else {
false
}
}
pub(crate) fn update(&mut self, room_id: &str, event: &PresenceEvent) {
self.last_active = event.content.last_active_ago;
self.current_room_id =
Some(RoomId::try_from(room_id).expect("room id failed CurrentRoom::update"));
}
}
/// A no IO Client implementation. /// A no IO Client implementation.
/// ///
/// This Client is a state machine that receives responses and events and /// This Client is a state machine that receives responses and events and
@ -100,8 +72,6 @@ pub struct Client {
pub sync_token: Option<Token>, pub sync_token: Option<Token>,
/// A map of the rooms our user is joined in. /// A map of the rooms our user is joined in.
pub joined_rooms: HashMap<String, Arc<Mutex<Room>>>, pub joined_rooms: HashMap<String, Arc<Mutex<Room>>>,
/// The most recent room the logged in user used by `RoomId`.
pub current_room_id: CurrentRoom,
/// A list of ignored users. /// A list of ignored users.
pub ignored_users: Vec<UserId>, pub ignored_users: Vec<UserId>,
/// The push ruleset for the logged in user. /// The push ruleset for the logged in user.
@ -120,7 +90,6 @@ impl fmt::Debug for Client {
.field("session", &self.session) .field("session", &self.session)
.field("sync_token", &self.sync_token) .field("sync_token", &self.sync_token)
.field("joined_rooms", &self.joined_rooms) .field("joined_rooms", &self.joined_rooms)
.field("current_room_id", &self.current_room_id)
.field("ignored_users", &self.ignored_users) .field("ignored_users", &self.ignored_users)
.field("push_ruleset", &self.push_ruleset) .field("push_ruleset", &self.push_ruleset)
.field("event_emitter", &"EventEmitter<...>") .field("event_emitter", &"EventEmitter<...>")
@ -146,7 +115,6 @@ impl Client {
session, session,
sync_token: None, sync_token: None,
joined_rooms: HashMap::new(), joined_rooms: HashMap::new(),
current_room_id: CurrentRoom::default(),
ignored_users: Vec::new(), ignored_users: Vec::new(),
push_ruleset: None, push_ruleset: None,
event_emitter: None, event_emitter: None,
@ -160,6 +128,16 @@ impl Client {
self.session.is_some() self.session.is_some()
} }
/// Add `EventEmitter` to `Client`.
///
/// The methods of `EventEmitter` are called when the respective `RoomEvents` occur.
pub async fn add_event_emitter(
&mut self,
emitter: Arc<tokio::sync::Mutex<Box<dyn EventEmitter>>>,
) {
self.event_emitter = Some(emitter);
}
/// Receive a login response and update the session of the client. /// Receive a login response and update the session of the client.
/// ///
/// # Arguments /// # Arguments
@ -204,10 +182,6 @@ impl Client {
res res
} }
pub(crate) fn current_room_id(&self) -> Option<RoomId> {
self.current_room_id.current_room_id.clone()
}
pub(crate) fn get_or_create_room(&mut self, room_id: &str) -> &mut Arc<Mutex<Room>> { pub(crate) fn get_or_create_room(&mut self, room_id: &str) -> &mut Arc<Mutex<Room>> {
#[allow(clippy::or_fun_call)] #[allow(clippy::or_fun_call)]
self.joined_rooms self.joined_rooms
@ -336,15 +310,6 @@ impl Client {
/// ///
/// * `event` - The event that should be handled by the client. /// * `event` - The event that should be handled by the client.
pub async fn receive_presence_event(&mut self, room_id: &str, event: &PresenceEvent) -> bool { pub async fn receive_presence_event(&mut self, room_id: &str, event: &PresenceEvent) -> bool {
let user_id = &self
.session
.as_ref()
.expect("to receive events you must be logged in")
.user_id;
if self.current_room_id.comes_after(user_id, event) {
self.current_room_id.update(room_id, event);
}
// this should be the room that was just created in the `Client::sync` loop. // this should be the room that was just created in the `Client::sync` loop.
if let Some(room) = self.get_room(room_id) { if let Some(room) = self.get_room(room_id) {
let mut room = room.lock().await; let mut room = room.lock().await;
@ -361,6 +326,8 @@ impl Client {
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `room_id` - The unique id of the room the event belongs to.
///
/// * `event` - The presence event for a specified room member. /// * `event` - The presence event for a specified room member.
pub async fn receive_account_data(&mut self, room_id: &str, event: &NonRoomEvent) -> bool { pub async fn receive_account_data(&mut self, room_id: &str, event: &NonRoomEvent) -> bool {
match event { match event {

View File

@ -16,53 +16,69 @@
use std::sync::Arc; use std::sync::Arc;
use crate::events::{ use crate::events::{
call::{
answer::AnswerEvent, candidates::CandidatesEvent, hangup::HangupEvent, invite::InviteEvent,
},
direct::DirectEvent,
dummy::DummyEvent,
forwarded_room_key::ForwardedRoomKeyEvent,
fully_read::FullyReadEvent, fully_read::FullyReadEvent,
ignored_user_list::IgnoredUserListEvent, ignored_user_list::IgnoredUserListEvent,
key::verification::{
accept::AcceptEvent, cancel::CancelEvent, key::KeyEvent, mac::MacEvent,
request::RequestEvent, start::StartEvent,
},
presence::PresenceEvent, presence::PresenceEvent,
push_rules::PushRulesEvent, push_rules::PushRulesEvent,
receipt::ReceiptEvent,
room::{ room::{
aliases::AliasesEvent, aliases::AliasesEvent,
avatar::AvatarEvent, avatar::AvatarEvent,
canonical_alias::CanonicalAliasEvent, canonical_alias::CanonicalAliasEvent,
create::CreateEvent,
encrypted::EncryptedEvent,
encryption::EncryptionEvent,
guest_access::GuestAccessEvent,
history_visibility::HistoryVisibilityEvent,
join_rules::JoinRulesEvent, join_rules::JoinRulesEvent,
member::MemberEvent, member::MemberEvent,
message::{feedback::FeedbackEvent, MessageEvent}, message::{feedback::FeedbackEvent, MessageEvent},
name::NameEvent, name::NameEvent,
pinned_events::PinnedEventsEvent,
power_levels::PowerLevelsEvent, power_levels::PowerLevelsEvent,
redaction::RedactionEvent, redaction::RedactionEvent,
server_acl::ServerAclEvent,
third_party_invite::ThirdPartyInviteEvent,
tombstone::TombstoneEvent,
topic::TopicEvent,
}, },
room_key::RoomKeyEvent,
room_key_request::RoomKeyRequestEvent,
sticker::StickerEvent,
tag::TagEvent,
typing::TypingEvent,
CustomEvent, CustomRoomEvent, CustomStateEvent,
}; };
use crate::models::Room; use crate::models::Room;
use tokio::sync::Mutex; use tokio::sync::Mutex;
/// This trait allows any type implementing `EventEmitter` to specify event callbacks for each event.
/// The `AsyncClient` calls each method when the corresponding event is received.
/// ///
/// # Examples
/// ```
/// # use std::ops::Deref;
/// # use std::sync::Arc;
/// # use std::{env, process::exit};
/// # use url::Url;
/// use matrix_sdk::{
/// self,
/// events::{
/// room::message::{MessageEvent, MessageEventContent, TextMessageEventContent},
/// },
/// AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings,
/// };
/// use tokio::sync::Mutex;
///
/// struct EventCallback;
///
/// #[async_trait::async_trait]
/// impl EventEmitter for EventCallback {
/// async fn on_room_message(&mut self, room: Arc<Mutex<Room>>, event: Arc<Mutex<MessageEvent>>) {
/// if let MessageEvent {
/// content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }),
/// sender,
/// ..
/// } = event.lock().await.deref()
/// {
/// let rooms = room.lock().await;
/// let member = rooms.members.get(&sender.to_string()).unwrap();
/// println!(
/// "{}: {}",
/// member
/// .user
/// .display_name
/// .as_ref()
/// .unwrap_or(&sender.to_string()),
/// msg_body
/// );
/// }
/// }
/// }
/// ```
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait EventEmitter: Send + Sync { pub trait EventEmitter: Send + Sync {
// ROOM EVENTS from `IncomingTimeline` // ROOM EVENTS from `IncomingTimeline`
@ -146,3 +162,173 @@ pub trait EventEmitter: Send + Sync {
/// Fires when `AsyncClient` receives a `NonRoomEvent::RoomAliases` event. /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomAliases` event.
async fn on_presence_event(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<PresenceEvent>>) {} async fn on_presence_event(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<PresenceEvent>>) {}
} }
#[cfg(test)]
mod test {
use super::*;
pub struct EvEmitterTest(Arc<Mutex<Vec<String>>>);
#[async_trait::async_trait]
impl EventEmitter for EvEmitterTest {
async fn on_room_member(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<MemberEvent>>) {
self.0.lock().await.push("member".to_string())
}
async fn on_room_name(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<NameEvent>>) {
self.0.lock().await.push("name".to_string())
}
async fn on_room_canonical_alias(
&mut self,
r: Arc<Mutex<Room>>,
_: Arc<Mutex<CanonicalAliasEvent>>,
) {
self.0.lock().await.push("canonical".to_string())
}
async fn on_room_aliases(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<AliasesEvent>>) {
self.0.lock().await.push("aliases".to_string())
}
async fn on_room_avatar(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<AvatarEvent>>) {
self.0.lock().await.push("avatar".to_string())
}
async fn on_room_message(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<MessageEvent>>) {
self.0.lock().await.push("message".to_string())
}
async fn on_room_message_feedback(
&mut self,
_: Arc<Mutex<Room>>,
_: Arc<Mutex<FeedbackEvent>>,
) {
self.0.lock().await.push("feedback".to_string())
}
async fn on_room_redaction(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<RedactionEvent>>) {
self.0.lock().await.push("redaction".to_string())
}
async fn on_room_power_levels(
&mut self,
_: Arc<Mutex<Room>>,
_: Arc<Mutex<PowerLevelsEvent>>,
) {
self.0.lock().await.push("power".to_string())
}
async fn on_state_member(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<MemberEvent>>) {
self.0.lock().await.push("state member".to_string())
}
async fn on_state_name(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<NameEvent>>) {
self.0.lock().await.push("state name".to_string())
}
async fn on_state_canonical_alias(
&mut self,
_: Arc<Mutex<Room>>,
_: Arc<Mutex<CanonicalAliasEvent>>,
) {
self.0.lock().await.push("state canonical".to_string())
}
async fn on_state_aliases(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<AliasesEvent>>) {
self.0.lock().await.push("state aliases".to_string())
}
async fn on_state_avatar(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<AvatarEvent>>) {
self.0.lock().await.push("state avatar".to_string())
}
async fn on_state_power_levels(
&mut self,
_: Arc<Mutex<Room>>,
_: Arc<Mutex<PowerLevelsEvent>>,
) {
self.0.lock().await.push("state power".to_string())
}
async fn on_state_join_rules(
&mut self,
_: Arc<Mutex<Room>>,
_: Arc<Mutex<JoinRulesEvent>>,
) {
self.0.lock().await.push("state rules".to_string())
}
async fn on_account_presence(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<PresenceEvent>>) {
self.0.lock().await.push("account presence".to_string())
}
async fn on_account_ignored_users(
&mut self,
_: Arc<Mutex<Room>>,
_: Arc<Mutex<IgnoredUserListEvent>>,
) {
self.0.lock().await.push("account ignore".to_string())
}
async fn on_account_push_rules(
&mut self,
_: Arc<Mutex<Room>>,
_: Arc<Mutex<PushRulesEvent>>,
) {
self.0.lock().await.push("".to_string())
}
async fn on_account_data_fully_read(
&mut self,
_: Arc<Mutex<Room>>,
_: Arc<Mutex<FullyReadEvent>>,
) {
self.0.lock().await.push("account read".to_string())
}
async fn on_presence_event(&mut self, _: Arc<Mutex<Room>>, _: Arc<Mutex<PresenceEvent>>) {
self.0.lock().await.push("presence event".to_string())
}
}
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 event_emitter() {
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("tests/data/sync.json")
.create();
let vec = Arc::new(Mutex::new(Vec::new()));
let test_vec = Arc::clone(&vec);
let mut emitter = Arc::new(Mutex::new(
Box::new(EvEmitterTest(vec)) as Box<(dyn EventEmitter)>
));
let mut client = AsyncClient::new(homeserver, Some(session)).unwrap();
client.add_event_emitter(Arc::clone(&emitter)).await;
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
let _response = client.sync(sync_settings).await.unwrap();
let v = test_vec.lock().await;
assert_eq!(
v.as_slice(),
[
"state rules",
"state member",
"state aliases",
"state power",
"state canonical",
"state member",
"state member",
"message",
"account read",
"account ignore",
"presence event",
],
)
}
}

View File

@ -28,12 +28,11 @@
pub use crate::{error::Error, error::Result, session::Session}; pub use crate::{error::Error, error::Result, session::Session};
pub use reqwest::header::InvalidHeaderValue; pub use reqwest::header::InvalidHeaderValue;
pub use ruma_api;
pub use ruma_client_api as api; pub use ruma_client_api as api;
pub use ruma_events as events; pub use ruma_events as events;
pub use ruma_identifiers as identifiers; pub use ruma_identifiers as identifiers;
pub use ruma_api as ruma_traits;
mod async_client; mod async_client;
mod base_client; mod base_client;
mod error; mod error;

View File

@ -87,33 +87,3 @@ async fn room_names() {
client.get_room_name("!SVkFJHzfwvuaIEawgC:localhost").await client.get_room_name("!SVkFJHzfwvuaIEawgC:localhost").await
); );
} }
#[tokio::test]
async fn current_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(
"GET",
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
)
.with_status(200)
.with_body_from_file("tests/data/sync.json")
.create();
let mut 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();
assert_eq!(
Some("!SVkFJHzfwvuaIEawgC:localhost".into()),
client.current_room_id().await.map(|id| id.to_string())
);
}