diff --git a/examples/login.rs b/examples/login.rs index bf5e19f9..397766f2 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -1,14 +1,11 @@ -use std::ops::{Deref, DerefMut}; -use std::sync::{Arc, RwLock}; +use std::ops::Deref; +use std::sync::Arc; use std::{env, process::exit}; use url::Url; use matrix_sdk::{ self, - events::{ - collections::all::RoomEvent, - room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, - }, + events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings, }; use tokio::sync::Mutex; diff --git a/src/async_client.rs b/src/async_client.rs index 57909698..9c914310 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -270,7 +270,9 @@ impl AsyncClient { 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 { self.base_client .read() @@ -279,21 +281,20 @@ impl AsyncClient { .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 { 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>> { 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 { - self.base_client.read().await.current_room_id() - } - /// Login to the server. /// /// # Arguments diff --git a/src/base_client.rs b/src/base_client.rs index 6341a10b..3c98a879 100644 --- a/src/base_client.rs +++ b/src/base_client.rs @@ -35,7 +35,6 @@ use crate::models::Room; use crate::session::Session; use crate::EventEmitter; -use js_int::UInt; use tokio::sync::Mutex; #[cfg(feature = "encryption")] @@ -61,33 +60,6 @@ pub struct RoomName { aliases: Vec, } -#[derive(Clone, Debug, Default)] -pub struct CurrentRoom { - last_active: Option, - current_room_id: Option, -} - -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. /// /// This Client is a state machine that receives responses and events and @@ -100,8 +72,6 @@ pub struct Client { pub sync_token: Option, /// A map of the rooms our user is joined in. pub joined_rooms: HashMap>>, - /// The most recent room the logged in user used by `RoomId`. - pub current_room_id: CurrentRoom, /// A list of ignored users. pub ignored_users: Vec, /// The push ruleset for the logged in user. @@ -120,7 +90,6 @@ impl fmt::Debug for Client { .field("session", &self.session) .field("sync_token", &self.sync_token) .field("joined_rooms", &self.joined_rooms) - .field("current_room_id", &self.current_room_id) .field("ignored_users", &self.ignored_users) .field("push_ruleset", &self.push_ruleset) .field("event_emitter", &"EventEmitter<...>") @@ -146,7 +115,6 @@ impl Client { session, sync_token: None, joined_rooms: HashMap::new(), - current_room_id: CurrentRoom::default(), ignored_users: Vec::new(), push_ruleset: None, event_emitter: None, @@ -160,6 +128,16 @@ impl Client { 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>>, + ) { + self.event_emitter = Some(emitter); + } + /// Receive a login response and update the session of the client. /// /// # Arguments @@ -204,10 +182,6 @@ impl Client { res } - pub(crate) fn current_room_id(&self) -> Option { - self.current_room_id.current_room_id.clone() - } - pub(crate) fn get_or_create_room(&mut self, room_id: &str) -> &mut Arc> { #[allow(clippy::or_fun_call)] self.joined_rooms @@ -336,15 +310,6 @@ impl 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 { - 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. if let Some(room) = self.get_room(room_id) { let mut room = room.lock().await; @@ -361,6 +326,8 @@ impl Client { /// /// # Arguments /// + /// * `room_id` - The unique id of the room the event belongs to. + /// /// * `event` - The presence event for a specified room member. pub async fn receive_account_data(&mut self, room_id: &str, event: &NonRoomEvent) -> bool { match event { diff --git a/src/event_emitter/mod.rs b/src/event_emitter/mod.rs index 71c7377b..d7a27278 100644 --- a/src/event_emitter/mod.rs +++ b/src/event_emitter/mod.rs @@ -16,53 +16,69 @@ use std::sync::Arc; use crate::events::{ - call::{ - answer::AnswerEvent, candidates::CandidatesEvent, hangup::HangupEvent, invite::InviteEvent, - }, - direct::DirectEvent, - dummy::DummyEvent, - forwarded_room_key::ForwardedRoomKeyEvent, fully_read::FullyReadEvent, ignored_user_list::IgnoredUserListEvent, - key::verification::{ - accept::AcceptEvent, cancel::CancelEvent, key::KeyEvent, mac::MacEvent, - request::RequestEvent, start::StartEvent, - }, presence::PresenceEvent, push_rules::PushRulesEvent, - receipt::ReceiptEvent, room::{ aliases::AliasesEvent, avatar::AvatarEvent, canonical_alias::CanonicalAliasEvent, - create::CreateEvent, - encrypted::EncryptedEvent, - encryption::EncryptionEvent, - guest_access::GuestAccessEvent, - history_visibility::HistoryVisibilityEvent, join_rules::JoinRulesEvent, member::MemberEvent, message::{feedback::FeedbackEvent, MessageEvent}, name::NameEvent, - pinned_events::PinnedEventsEvent, power_levels::PowerLevelsEvent, 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 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>, event: Arc>) { +/// 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] pub trait EventEmitter: Send + Sync { // ROOM EVENTS from `IncomingTimeline` @@ -146,3 +162,173 @@ pub trait EventEmitter: Send + Sync { /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomAliases` event. async fn on_presence_event(&mut self, _: Arc>, _: Arc>) {} } + +#[cfg(test)] +mod test { + use super::*; + + pub struct EvEmitterTest(Arc>>); + + #[async_trait::async_trait] + impl EventEmitter for EvEmitterTest { + async fn on_room_member(&mut self, _: Arc>, _: Arc>) { + self.0.lock().await.push("member".to_string()) + } + async fn on_room_name(&mut self, _: Arc>, _: Arc>) { + self.0.lock().await.push("name".to_string()) + } + async fn on_room_canonical_alias( + &mut self, + r: Arc>, + _: Arc>, + ) { + self.0.lock().await.push("canonical".to_string()) + } + async fn on_room_aliases(&mut self, _: Arc>, _: Arc>) { + self.0.lock().await.push("aliases".to_string()) + } + async fn on_room_avatar(&mut self, _: Arc>, _: Arc>) { + self.0.lock().await.push("avatar".to_string()) + } + async fn on_room_message(&mut self, _: Arc>, _: Arc>) { + self.0.lock().await.push("message".to_string()) + } + async fn on_room_message_feedback( + &mut self, + _: Arc>, + _: Arc>, + ) { + self.0.lock().await.push("feedback".to_string()) + } + async fn on_room_redaction(&mut self, _: Arc>, _: Arc>) { + self.0.lock().await.push("redaction".to_string()) + } + async fn on_room_power_levels( + &mut self, + _: Arc>, + _: Arc>, + ) { + self.0.lock().await.push("power".to_string()) + } + + async fn on_state_member(&mut self, _: Arc>, _: Arc>) { + self.0.lock().await.push("state member".to_string()) + } + async fn on_state_name(&mut self, _: Arc>, _: Arc>) { + self.0.lock().await.push("state name".to_string()) + } + async fn on_state_canonical_alias( + &mut self, + _: Arc>, + _: Arc>, + ) { + self.0.lock().await.push("state canonical".to_string()) + } + async fn on_state_aliases(&mut self, _: Arc>, _: Arc>) { + self.0.lock().await.push("state aliases".to_string()) + } + async fn on_state_avatar(&mut self, _: Arc>, _: Arc>) { + self.0.lock().await.push("state avatar".to_string()) + } + async fn on_state_power_levels( + &mut self, + _: Arc>, + _: Arc>, + ) { + self.0.lock().await.push("state power".to_string()) + } + async fn on_state_join_rules( + &mut self, + _: Arc>, + _: Arc>, + ) { + self.0.lock().await.push("state rules".to_string()) + } + + async fn on_account_presence(&mut self, _: Arc>, _: Arc>) { + self.0.lock().await.push("account presence".to_string()) + } + async fn on_account_ignored_users( + &mut self, + _: Arc>, + _: Arc>, + ) { + self.0.lock().await.push("account ignore".to_string()) + } + async fn on_account_push_rules( + &mut self, + _: Arc>, + _: Arc>, + ) { + self.0.lock().await.push("".to_string()) + } + async fn on_account_data_fully_read( + &mut self, + _: Arc>, + _: Arc>, + ) { + self.0.lock().await.push("account read".to_string()) + } + async fn on_presence_event(&mut self, _: Arc>, _: Arc>) { + 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", + ], + ) + } +} diff --git a/src/lib.rs b/src/lib.rs index 69b29c1f..c8ab3964 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,12 +28,11 @@ pub use crate::{error::Error, error::Result, session::Session}; pub use reqwest::header::InvalidHeaderValue; +pub use ruma_api; pub use ruma_client_api as api; pub use ruma_events as events; pub use ruma_identifiers as identifiers; -pub use ruma_api as ruma_traits; - mod async_client; mod base_client; mod error; diff --git a/tests/async_client_tests.rs b/tests/async_client_tests.rs index f152c82e..cec0b25b 100644 --- a/tests/async_client_tests.rs +++ b/tests/async_client_tests.rs @@ -87,33 +87,3 @@ async fn room_names() { 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()) - ); -}