add test and docs for EventEmitter, fix review issues
parent
55c25ce6ba
commit
187734f449
|
@ -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;
|
||||
|
|
|
@ -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<String> {
|
||||
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<String> {
|
||||
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>>> {
|
||||
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.
|
||||
///
|
||||
/// # Arguments
|
||||
|
|
|
@ -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<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.
|
||||
///
|
||||
/// This Client is a state machine that receives responses and events and
|
||||
|
@ -100,8 +72,6 @@ pub struct Client {
|
|||
pub sync_token: Option<Token>,
|
||||
/// A map of the rooms our user is joined in.
|
||||
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.
|
||||
pub ignored_users: Vec<UserId>,
|
||||
/// 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<tokio::sync::Mutex<Box<dyn EventEmitter>>>,
|
||||
) {
|
||||
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<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>> {
|
||||
#[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 {
|
||||
|
|
|
@ -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<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]
|
||||
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<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",
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue