diff --git a/src/async_client.rs b/src/async_client.rs index 6b4c7862..6a125dec 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -284,6 +284,11 @@ impl AsyncClient { self.base_client.read().await.calculate_room_names() } + /// Calculate the room names this client knows about. + pub async fn current_room_id(&self) -> Option { + self.base_client.read().await.current_room_id() + } + /// Add a callback that will be called every time the client receives a room /// event /// diff --git a/src/base_client.rs b/src/base_client.rs index f3c76353..23d501ac 100644 --- a/src/base_client.rs +++ b/src/base_client.rs @@ -14,6 +14,7 @@ // limitations under the License. use std::collections::HashMap; +use std::convert::TryFrom; #[cfg(feature = "encryption")] use std::result::Result as StdResult; @@ -22,6 +23,10 @@ use crate::api::r0 as api; use crate::error::Result; use crate::events::collections::all::{RoomEvent, StateEvent}; use crate::events::presence::PresenceEvent; +// `NonRoomEvent` is what it is aliased as +use crate::events::collections::only::Event as NonRoomEvent; +use crate::events::ignored_user_list::IgnoredUserListEvent; +use crate::events::push_rules::{ Ruleset, PushRulesEvent}; use crate::events::room::{ aliases::AliasesEvent, canonical_alias::CanonicalAliasEvent, @@ -29,11 +34,13 @@ use crate::events::room::{ name::NameEvent, }; use crate::events::EventResult; -use crate::identifiers::RoomAliasId; +use crate::identifiers::{RoomAliasId, UserId as Uid}; use crate::models::Room; use crate::session::Session; use std::sync::{Arc, RwLock}; +use js_int::UInt; + #[cfg(feature = "encryption")] use tokio::sync::Mutex; @@ -57,6 +64,27 @@ pub struct RoomName { aliases: Vec, } +#[derive(Clone, Debug, Default)] +pub struct CurrentRoom { + last_active: Option, + current_room_id: Option, +} + +impl CurrentRoom { + pub(crate) fn comes_after(&self, user: &Uid, event: &PresenceEvent) -> bool { + if user == &event.sender { + 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")); + } +} + #[derive(Debug)] /// A no IO Client implementation. /// @@ -70,6 +98,13 @@ 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. + pub push_ruleset: Option, + #[cfg(feature = "encryption")] olm: Arc>>, } @@ -92,6 +127,9 @@ impl Client { session, sync_token: None, joined_rooms: HashMap::new(), + current_room_id: CurrentRoom::default(), + ignored_users: Vec::new(), + push_ruleset: None, #[cfg(feature = "encryption")] olm: Arc::new(Mutex::new(olm)), }) @@ -147,6 +185,10 @@ impl Client { .collect() } + 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 @@ -162,6 +204,32 @@ impl Client { )))) } + /// Handle a m.ignored_user_list event, updating the room state if necessary. + /// + /// Returns true if the room name changed, false otherwise. + pub(crate) fn handle_ignored_users(&mut self, event: &IgnoredUserListEvent) -> bool { + // TODO use actual UserId instead of string? + if self.ignored_users == event.content.ignored_users.iter().map(|u| u.to_string()).collect::>() { + false + } else { + self.ignored_users = event.content.ignored_users.iter().map(|u| u.to_string()).collect(); + true + } + } + + /// Handle a m.ignored_user_list event, updating the room state if necessary. + /// + /// Returns true if the room name changed, false otherwise. + pub(crate) fn handle_push_rules(&mut self, event: &PushRulesEvent) -> bool { + // TODO this is basically a stub + if self.push_ruleset.as_ref() == Some(&event.content.global) { + false + } else { + self.push_ruleset = Some(event.content.global.clone()); + true + } + } + /// Receive a timeline event for a joined room and update the client state. /// /// If the event was a encrypted room event and decryption was successful @@ -236,11 +304,32 @@ impl Client { /// /// * `event` - The event that should be handled by the client. pub 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 guaranteed to find the room that was just created in the `Client::sync` loop. let mut room = self.get_or_create_room(room_id).write().unwrap(); room.receive_presence_event(event) } + /// Receive a presence event from an `IncomingResponse` and updates the client state. + /// + /// This will only update the user if found in the current room looped through by `AsyncClient::sync`. + /// Returns true if the specific users presence has changed, false otherwise. + /// + /// # Arguments + /// + /// * `event` - The presence event for a specified room member. + pub fn receive_account_data(&mut self, room_id: &str, event: &NonRoomEvent) -> bool { + match event { + NonRoomEvent::IgnoredUserList(iu) => self.handle_ignored_users(iu), + NonRoomEvent::Presence(p) => self.receive_presence_event(room_id, p), + NonRoomEvent::PushRules(pr) => self.handle_push_rules(pr), + _ => false, + } + } + /// Receive a response from a sync call. /// /// # Arguments diff --git a/src/models/room.rs b/src/models/room.rs index b354c575..bdbea9a8 100644 --- a/src/models/room.rs +++ b/src/models/room.rs @@ -17,7 +17,7 @@ use std::collections::HashMap; use std::sync::{Arc, RwLock}; use super::{RoomMember, User, UserId}; -use crate::api::r0 as api; + use crate::events::collections::all::{RoomEvent, StateEvent}; use crate::events::room::{ aliases::AliasesEvent, @@ -33,6 +33,8 @@ use crate::events::{ use crate::identifiers::RoomAliasId; use crate::session::Session; +use js_int::UInt; + #[cfg(feature = "encryption")] use tokio::sync::Mutex; @@ -69,6 +71,10 @@ pub struct Room { pub typing_users: Vec, /// A flag indicating if the room is encrypted. pub encrypted: bool, + /// Number of unread notifications with highlight flag set. + pub unread_highlight: Option, + /// Number of unread notifications. + pub unread_notifications: Option, } impl RoomName { @@ -133,6 +139,8 @@ impl Room { members: HashMap::new(), typing_users: Vec::new(), encrypted: false, + unread_highlight: None, + unread_notifications: None, } } @@ -148,22 +156,6 @@ impl Room { true } - /// Handle a room.member updating the room state if necessary. - /// - /// Returns true if the joined member list changed, false otherwise. - pub fn handle_membership(&mut self, event: &MemberEvent) -> bool { - match event.membership_change() { - MembershipChange::Invited | MembershipChange::Joined => self.add_member(event), - _ => { - if let Some(member) = self.members.get_mut(&event.sender.to_string()) { - member.update_member(event) - } else { - false - } - } - } - } - /// Add to the list of `RoomAliasId`s. fn room_aliases(&mut self, alias: &RoomAliasId) -> bool { self.room_name.push_alias(alias.clone()); @@ -181,6 +173,22 @@ impl Room { true } + /// Handle a room.member updating the room state if necessary. + /// + /// Returns true if the joined member list changed, false otherwise. + pub fn handle_membership(&mut self, event: &MemberEvent) -> bool { + match event.membership_change() { + MembershipChange::Invited | MembershipChange::Joined => self.add_member(event), + _ => { + if let Some(member) = self.members.get_mut(&event.sender.to_string()) { + member.update_member(event) + } else { + false + } + } + } + } + /// Handle a room.aliases event, updating the room state if necessary. /// /// Returns true if the room name changed, false otherwise. @@ -263,6 +271,7 @@ impl Room { /// Receive a presence event from an `IncomingResponse` and updates the client state. /// + /// This will only update the user if found in the current room looped through by `AsyncClient::sync`. /// Returns true if the specific users presence has changed, false otherwise. /// /// # Arguments