diff --git a/matrix_sdk/examples/autojoin.rs b/matrix_sdk/examples/autojoin.rs index a5edc34b..76f9938f 100644 --- a/matrix_sdk/examples/autojoin.rs +++ b/matrix_sdk/examples/autojoin.rs @@ -4,7 +4,7 @@ use tokio::time::{delay_for, Duration}; use matrix_sdk::{ self, events::{room::member::MemberEventContent, StrippedStateEvent}, - Client, ClientConfig, EventEmitter, SyncRoom, SyncSettings, + Client, ClientConfig, EventEmitter, RoomState, SyncSettings, }; use matrix_sdk_common_macros::async_trait; use url::Url; @@ -23,7 +23,7 @@ impl AutoJoinBot { impl EventEmitter for AutoJoinBot { async fn on_stripped_state_member( &self, - room: SyncRoom, + room: RoomState, room_member: &StrippedStateEvent, _: Option, ) { @@ -31,29 +31,30 @@ impl EventEmitter for AutoJoinBot { return; } - if let SyncRoom::Invited(room) = room { - let room = room.read().await; - println!("Autojoining room {}", room.room_id); + if let RoomState::Invited(room) = room { + println!("Autojoining room {}", room.room_id()); let mut delay = 2; - while let Err(err) = self.client.join_room_by_id(&room.room_id).await { + while let Err(err) = self.client.join_room_by_id(room.room_id()).await { // retry autojoin due to synapse sending invites, before the // invited user can join for more information see // https://github.com/matrix-org/synapse/issues/4345 eprintln!( "Failed to join room {} ({:?}), retrying in {}s", - room.room_id, err, delay + room.room_id(), + err, + delay ); delay_for(Duration::from_secs(delay)).await; delay *= 2; if delay > 3600 { - eprintln!("Can't join room {} ({:?})", room.room_id, err); + eprintln!("Can't join room {} ({:?})", room.room_id(), err); break; } } - println!("Successfully joined room {}", room.room_id); + println!("Successfully joined room {}", room.room_id()); } } } @@ -69,7 +70,7 @@ async fn login_and_sync( let client_config = ClientConfig::new().store_path(home); let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); - let mut client = Client::new_with_config(homeserver_url, client_config).unwrap(); + let client = Client::new_with_config(homeserver_url, client_config).unwrap(); client .login(username, password, None, Some("autojoin bot")) diff --git a/matrix_sdk/examples/command_bot.rs b/matrix_sdk/examples/command_bot.rs index 8c12fd5a..2a103789 100644 --- a/matrix_sdk/examples/command_bot.rs +++ b/matrix_sdk/examples/command_bot.rs @@ -6,7 +6,7 @@ use matrix_sdk::{ room::message::{MessageEventContent, TextMessageEventContent}, AnyMessageEventContent, SyncMessageEvent, }, - Client, ClientConfig, EventEmitter, JsonStore, SyncRoom, SyncSettings, + Client, ClientConfig, EventEmitter, RoomState, SyncSettings, }; use matrix_sdk_common_macros::async_trait; use url::Url; @@ -25,8 +25,12 @@ impl CommandBot { #[async_trait] impl EventEmitter for CommandBot { - async fn on_room_message(&self, room: SyncRoom, event: &SyncMessageEvent) { - if let SyncRoom::Joined(room) = room { + async fn on_room_message( + &self, + room: RoomState, + event: &SyncMessageEvent, + ) { + if let RoomState::Joined(room) = room { let msg_body = if let SyncMessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), .. @@ -41,15 +45,13 @@ impl EventEmitter for CommandBot { let content = AnyMessageEventContent::RoomMessage(MessageEventContent::text_plain( "🎉🎊🥳 let's PARTY!! 🥳🎊🎉", )); - // we clone here to hold the lock for as little time as possible. - let room_id = room.read().await.room_id.clone(); println!("sending"); self.client // send our message to the room we found the "!party" command in // the last parameter is an optional Uuid which we don't care about. - .room_send(&room_id, content, None) + .room_send(room.room_id(), content, None) .await .unwrap(); @@ -68,15 +70,14 @@ async fn login_and_sync( let mut home = dirs::home_dir().expect("no home directory found"); home.push("party_bot"); - let store = JsonStore::open(&home)?; let client_config = ClientConfig::new() .proxy("http://localhost:8080")? .disable_ssl_verification() - .state_store(Box::new(store)); + .store_path(home); let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); // create a new Client with the given homeserver url and config - let mut client = Client::new_with_config(homeserver_url, client_config).unwrap(); + let client = Client::new_with_config(homeserver_url, client_config).unwrap(); client .login(&username, &password, None, Some("command bot")) diff --git a/matrix_sdk/examples/image_bot.rs b/matrix_sdk/examples/image_bot.rs index 141d0d56..106c20c1 100644 --- a/matrix_sdk/examples/image_bot.rs +++ b/matrix_sdk/examples/image_bot.rs @@ -14,7 +14,7 @@ use matrix_sdk::{ room::message::{MessageEventContent, TextMessageEventContent}, SyncMessageEvent, }, - Client, ClientConfig, EventEmitter, SyncRoom, SyncSettings, + Client, ClientConfig, EventEmitter, RoomState, SyncSettings, }; use matrix_sdk_common_macros::async_trait; use url::Url; @@ -33,8 +33,12 @@ impl ImageBot { #[async_trait] impl EventEmitter for ImageBot { - async fn on_room_message(&self, room: SyncRoom, event: &SyncMessageEvent) { - if let SyncRoom::Joined(room) = room { + async fn on_room_message( + &self, + room: RoomState, + event: &SyncMessageEvent, + ) { + if let RoomState::Joined(room) = room { let msg_body = if let SyncMessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), .. @@ -46,13 +50,17 @@ impl EventEmitter for ImageBot { }; if msg_body.contains("!image") { - let room_id = room.read().await.room_id.clone(); - println!("sending image"); let mut image = self.image.lock().await; self.client - .room_send_attachment(&room_id, "cat", &mime::IMAGE_JPEG, &mut *image, None) + .room_send_attachment( + room.room_id(), + "cat", + &mime::IMAGE_JPEG, + &mut *image, + None, + ) .await .unwrap(); @@ -75,7 +83,7 @@ async fn login_and_sync( .disable_ssl_verification(); let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); - let mut client = Client::new_with_config(homeserver_url, client_config).unwrap(); + let client = Client::new_with_config(homeserver_url, client_config).unwrap(); client .login(&username, &password, None, Some("command bot")) diff --git a/matrix_sdk/examples/login.rs b/matrix_sdk/examples/login.rs index b63155b5..39407afb 100644 --- a/matrix_sdk/examples/login.rs +++ b/matrix_sdk/examples/login.rs @@ -7,7 +7,7 @@ use matrix_sdk::{ room::message::{MessageEventContent, TextMessageEventContent}, SyncMessageEvent, }, - Client, ClientConfig, EventEmitter, SyncRoom, SyncSettings, + Client, ClientConfig, EventEmitter, RoomState, SyncSettings, }; use matrix_sdk_common_macros::async_trait; @@ -15,21 +15,22 @@ struct EventCallback; #[async_trait] impl EventEmitter for EventCallback { - async fn on_room_message(&self, room: SyncRoom, event: &SyncMessageEvent) { - if let SyncRoom::Joined(room) = room { + async fn on_room_message( + &self, + room: RoomState, + event: &SyncMessageEvent, + ) { + if let RoomState::Joined(room) = room { if let SyncMessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), sender, .. } = event { - let name = { - // any reads should be held for the shortest time possible to - // avoid dead locks - let room = room.read().await; - let member = room.joined_members.get(&sender).unwrap(); - member.name() - }; + let member = room.get_member(&sender).await.unwrap(); + let name = member + .display_name() + .unwrap_or_else(|| member.user_id().as_str()); println!("{}: {}", name, msg_body); } } @@ -45,7 +46,7 @@ async fn login( .proxy("http://localhost:8080")? .disable_ssl_verification(); let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); - let mut client = Client::new_with_config(homeserver_url, client_config).unwrap(); + let client = Client::new_with_config(homeserver_url, client_config).unwrap(); client.add_event_emitter(Box::new(EventCallback)).await; diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 852655d7..6d81cdd7 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -41,7 +41,7 @@ use tracing::{debug, warn}; use tracing::{error, info, instrument}; use matrix_sdk_base::{ - responses::SyncResponse, BaseClient, BaseClientConfig, JoinedRoom, Room, Session, Store, + responses::SyncResponse, BaseClient, BaseClientConfig, EventEmitter, JoinedRoom, Session, Store, }; #[cfg(feature = "encryption")] @@ -549,11 +549,12 @@ impl Client { Ok(()) } - ///// Add `EventEmitter` to `Client`. - ///// - ///// The methods of `EventEmitter` are called when the respective `RoomEvents` occur. - //pub async fn add_event_emitter(&mut self, emitter: Box) { - //} + /// Add `EventEmitter` to `Client`. + /// + /// The methods of `EventEmitter` are called when the respective `RoomEvents` occur. + pub async fn add_event_emitter(&self, emitter: Box) { + self.base_client.add_event_emitter(emitter).await; + } // /// Returns the joined rooms this client knows about. // pub fn joined_rooms(&self) -> Arc>>>> { diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index 073c71e9..dc310d11 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -68,7 +68,7 @@ compile_error!("only one of 'native-tls' or 'rustls-tls' features can be enabled pub use matrix_sdk_base::crypto::LocalTrust; pub use matrix_sdk_base::{ Error as BaseError, EventEmitter, InvitedRoom, JoinedRoom, LeftRoom, RoomInfo, RoomMember, - Session, + RoomState, Session, }; pub use matrix_sdk_common::*; diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 5a720399..3685b997 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -58,6 +58,7 @@ use zeroize::Zeroizing; use crate::{ error::Result, + event_emitter::Emitter, responses::{ AccountData, Ephemeral, InviteState, InvitedRoom as InvitedRoomResponse, JoinedRoom as JoinedRoomResponse, LeftRoom as LeftRoomResponse, MemberEvent, Presence, @@ -188,7 +189,7 @@ pub struct BaseClient { store_passphrase: Arc>, /// Any implementor of EventEmitter will act as the callbacks for various /// events. - event_emitter: Arc>>>, + event_emitter: Arc>>, } #[cfg(not(tarpaulin_include))] @@ -419,6 +420,7 @@ impl BaseClient { /// /// The methods of `EventEmitter` are called when the respective `RoomEvents` occur. pub async fn add_event_emitter(&self, emitter: Box) { + let emitter = Emitter { inner: emitter }; *self.event_emitter.write().await = Some(emitter); } @@ -832,7 +834,7 @@ impl BaseClient { info!("Processed a sync response in {:?}", now.elapsed().unwrap()); - Ok(SyncResponse { + let response = SyncResponse { next_batch: response.next_batch, rooms, presence: Presence { @@ -849,11 +851,16 @@ impl BaseClient { .collect(), ..Default::default() - }) + }; + + if let Some(emitter) = self.event_emitter.read().await.as_ref() { + emitter.emit_sync(&response).await; + } + + Ok(response) } async fn apply_changes(&self, changes: &StateChanges) { - // TODO emit room changes here for (room_id, room_info) in &changes.room_infos { if let Some(room) = self.get_bare_room(&room_id) { room.update_summary(room_info.clone()) diff --git a/matrix_sdk_base/src/event_emitter/mod.rs b/matrix_sdk_base/src/event_emitter/mod.rs index 41637042..c74b9310 100644 --- a/matrix_sdk_base/src/event_emitter/mod.rs +++ b/matrix_sdk_base/src/event_emitter/mod.rs @@ -12,9 +12,9 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::Arc; +use std::ops::Deref; -use matrix_sdk_common::locks::RwLock; +use matrix_sdk_common::{events::AnySyncRoomEvent, identifiers::RoomId}; use serde_json::value::RawValue as RawJsonValue; use crate::{ @@ -38,13 +38,203 @@ use crate::{ tombstone::TombstoneEventContent, }, typing::TypingEventContent, - BasicEvent, StrippedStateEvent, SyncEphemeralRoomEvent, SyncMessageEvent, SyncStateEvent, + AnyBasicEvent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncMessageEvent, + AnySyncStateEvent, BasicEvent, StrippedStateEvent, SyncEphemeralRoomEvent, + SyncMessageEvent, SyncStateEvent, }, + responses::SyncResponse, rooms::RoomState, - Room, }; use matrix_sdk_common_macros::async_trait; +pub(crate) struct Emitter { + pub(crate) inner: Box, +} + +impl Deref for Emitter { + type Target = dyn EventEmitter; + + fn deref(&self) -> &Self::Target { + &*self.inner + } +} + +impl Emitter { + fn get_room(&self, _room_id: &RoomId) -> Option { + todo!() + } + + pub(crate) async fn emit_sync(&self, response: &SyncResponse) { + for (room_id, room_info) in &response.rooms.join { + if let Some(room) = self.get_room(room_id) { + for event in &room_info.ephemeral.events { + self.emit_ephemeral_event(room.clone(), event).await; + } + + for event in &room_info.account_data.events { + self.emit_account_data_event(room.clone(), event).await; + } + + for event in &room_info.state.events { + self.emit_state_event(room.clone(), event).await; + } + + for event in &room_info.timeline.events { + self.emit_timeline_event(room.clone(), event).await; + } + } + } + + for (room_id, room_info) in &response.rooms.leave { + if let Some(room) = self.get_room(room_id) { + for event in &room_info.account_data.events { + self.emit_account_data_event(room.clone(), event).await; + } + + for event in &room_info.state.events { + self.emit_state_event(room.clone(), event).await; + } + + for event in &room_info.timeline.events { + self.emit_timeline_event(room.clone(), event).await; + } + } + } + + for (room_id, room_info) in &response.rooms.invite { + if let Some(room) = self.get_room(room_id) { + for event in &room_info.invite_state.events { + self.emit_stripped_state_event(room.clone(), event).await; + } + } + } + + for event in &response.presence.events { + self.on_presence_event(event).await; + } + } + + async fn emit_timeline_event(&self, room: RoomState, event: &AnySyncRoomEvent) { + match event { + AnySyncRoomEvent::State(event) => match event { + AnySyncStateEvent::RoomMember(e) => self.on_room_member(room, e).await, + AnySyncStateEvent::RoomName(e) => self.on_room_name(room, e).await, + AnySyncStateEvent::RoomCanonicalAlias(e) => { + self.on_room_canonical_alias(room, e).await + } + AnySyncStateEvent::RoomAliases(e) => self.on_room_aliases(room, e).await, + AnySyncStateEvent::RoomAvatar(e) => self.on_room_avatar(room, e).await, + AnySyncStateEvent::RoomPowerLevels(e) => self.on_room_power_levels(room, e).await, + AnySyncStateEvent::RoomTombstone(e) => self.on_room_tombstone(room, e).await, + AnySyncStateEvent::RoomJoinRules(e) => self.on_room_join_rules(room, e).await, + AnySyncStateEvent::Custom(e) => { + self.on_custom_event(room, &CustomEvent::State(e)).await + } + _ => {} + }, + AnySyncRoomEvent::Message(event) => match event { + AnySyncMessageEvent::RoomMessage(e) => self.on_room_message(room, e).await, + AnySyncMessageEvent::RoomMessageFeedback(e) => { + self.on_room_message_feedback(room, e).await + } + AnySyncMessageEvent::RoomRedaction(e) => self.on_room_redaction(room, e).await, + AnySyncMessageEvent::Custom(e) => { + self.on_custom_event(room, &CustomEvent::Message(e)).await + } + _ => {} + }, + AnySyncRoomEvent::RedactedState(_event) => {} + AnySyncRoomEvent::RedactedMessage(_event) => {} + } + } + + async fn emit_state_event(&self, room: RoomState, event: &AnySyncStateEvent) { + match event { + AnySyncStateEvent::RoomMember(member) => self.on_state_member(room, &member).await, + AnySyncStateEvent::RoomName(name) => self.on_state_name(room, &name).await, + AnySyncStateEvent::RoomCanonicalAlias(canonical) => { + self.on_state_canonical_alias(room, &canonical).await + } + AnySyncStateEvent::RoomAliases(aliases) => self.on_state_aliases(room, &aliases).await, + AnySyncStateEvent::RoomAvatar(avatar) => self.on_state_avatar(room, &avatar).await, + AnySyncStateEvent::RoomPowerLevels(power) => { + self.on_state_power_levels(room, &power).await + } + AnySyncStateEvent::RoomJoinRules(rules) => self.on_state_join_rules(room, &rules).await, + AnySyncStateEvent::RoomTombstone(tomb) => { + // TODO make `on_state_tombstone` method + self.on_room_tombstone(room, &tomb).await + } + AnySyncStateEvent::Custom(custom) => { + self.on_custom_event(room, &CustomEvent::State(custom)) + .await + } + _ => {} + } + } + + pub(crate) async fn emit_stripped_state_event( + &self, + // TODO these events are only emitted in invited rooms. + room: RoomState, + event: &AnyStrippedStateEvent, + ) { + match event { + AnyStrippedStateEvent::RoomMember(member) => { + self.on_stripped_state_member(room, &member, None).await + } + AnyStrippedStateEvent::RoomName(name) => self.on_stripped_state_name(room, &name).await, + AnyStrippedStateEvent::RoomCanonicalAlias(canonical) => { + self.on_stripped_state_canonical_alias(room, &canonical) + .await + } + AnyStrippedStateEvent::RoomAliases(aliases) => { + self.on_stripped_state_aliases(room, &aliases).await + } + AnyStrippedStateEvent::RoomAvatar(avatar) => { + self.on_stripped_state_avatar(room, &avatar).await + } + AnyStrippedStateEvent::RoomPowerLevels(power) => { + self.on_stripped_state_power_levels(room, &power).await + } + AnyStrippedStateEvent::RoomJoinRules(rules) => { + self.on_stripped_state_join_rules(room, &rules).await + } + _ => {} + } + } + + pub(crate) async fn emit_account_data_event(&self, room: RoomState, event: &AnyBasicEvent) { + match event { + AnyBasicEvent::Presence(presence) => self.on_non_room_presence(room, &presence).await, + AnyBasicEvent::IgnoredUserList(ignored) => { + self.on_non_room_ignored_users(room, &ignored).await + } + AnyBasicEvent::PushRules(rules) => self.on_non_room_push_rules(room, &rules).await, + _ => {} + } + } + + pub(crate) async fn emit_ephemeral_event( + &self, + room: RoomState, + event: &AnySyncEphemeralRoomEvent, + ) { + match event { + AnySyncEphemeralRoomEvent::FullyRead(full_read) => { + self.on_non_room_fully_read(room, full_read).await + } + AnySyncEphemeralRoomEvent::Typing(typing) => { + self.on_non_room_typing(room, typing).await + } + AnySyncEphemeralRoomEvent::Receipt(receipt) => { + self.on_non_room_receipt(room, receipt).await + } + _ => {} + } + } +} + /// This represents the various "unrecognized" events. #[derive(Clone, Copy, Debug)] pub enum CustomEvent<'c> { @@ -259,7 +449,7 @@ pub trait EventEmitter: Send + Sync { // `PresenceEvent` is a struct so there is only the one method /// Fires when `Client` receives a `NonRoomEvent::RoomAliases` event. - async fn on_presence_event(&self, _: RoomState, _: &PresenceEvent) {} + async fn on_presence_event(&self, _: &PresenceEvent) {} /// Fires when `Client` receives a `Event::Custom` event or if deserialization fails /// because the event was unknown to ruma. @@ -477,7 +667,7 @@ mod test { ) { self.0.lock().await.push("receipt event".to_string()) } - async fn on_presence_event(&self, _: RoomState, _: &PresenceEvent) { + async fn on_presence_event(&self, _: &PresenceEvent) { self.0.lock().await.push("presence event".to_string()) } async fn on_unrecognized_event(&self, _: RoomState, _: &RawJsonValue) { diff --git a/matrix_sdk_base/src/rooms/mod.rs b/matrix_sdk_base/src/rooms/mod.rs index 2ce618e5..1d847eac 100644 --- a/matrix_sdk_base/src/rooms/mod.rs +++ b/matrix_sdk_base/src/rooms/mod.rs @@ -64,6 +64,7 @@ pub struct JoinedRoom { pub(crate) inner: Room, } +// TODO do we wan't to deref here or have separate implementations. impl Deref for JoinedRoom { type Target = Room;