Merge branch 'notifications'
commit
5ed0c7a7b3
|
@ -14,7 +14,9 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use matrix_sdk_common::{events::AnySyncRoomEvent, identifiers::RoomId};
|
use matrix_sdk_common::{
|
||||||
|
api::r0::push::get_notifications::Notification, events::AnySyncRoomEvent, identifiers::RoomId,
|
||||||
|
};
|
||||||
use serde_json::value::RawValue as RawJsonValue;
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -118,6 +120,15 @@ impl Handler {
|
||||||
for event in &response.presence.events {
|
for event in &response.presence.events {
|
||||||
self.on_presence_event(event).await;
|
self.on_presence_event(event).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (room_id, notifications) in &response.notifications {
|
||||||
|
if let Some(room) = self.get_room(&room_id) {
|
||||||
|
for notification in notifications {
|
||||||
|
self.on_room_notification(room.clone(), notification.clone())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_timeline_event(&self, room: Room, event: &AnySyncRoomEvent) {
|
async fn handle_timeline_event(&self, room: Room, event: &AnySyncRoomEvent) {
|
||||||
|
@ -348,6 +359,10 @@ pub trait EventHandler: Send + Sync {
|
||||||
/// Fires when `Client` receives a `RoomEvent::Tombstone` event.
|
/// Fires when `Client` receives a `RoomEvent::Tombstone` event.
|
||||||
async fn on_room_tombstone(&self, _: Room, _: &SyncStateEvent<TombstoneEventContent>) {}
|
async fn on_room_tombstone(&self, _: Room, _: &SyncStateEvent<TombstoneEventContent>) {}
|
||||||
|
|
||||||
|
/// Fires when `Client` receives room events that trigger notifications according to
|
||||||
|
/// the push rules of the user.
|
||||||
|
async fn on_room_notification(&self, _: Room, _: Notification) {}
|
||||||
|
|
||||||
// `RoomEvent`s from `IncomingState`
|
// `RoomEvent`s from `IncomingState`
|
||||||
/// Fires when `Client` receives a `StateEvent::RoomMember` event.
|
/// Fires when `Client` receives a `StateEvent::RoomMember` event.
|
||||||
async fn on_state_member(&self, _: Room, _: &SyncStateEvent<MemberEventContent>) {}
|
async fn on_state_member(&self, _: Room, _: &SyncStateEvent<MemberEventContent>) {}
|
||||||
|
@ -667,6 +682,9 @@ mod test {
|
||||||
async fn on_custom_event(&self, _: Room, _: &CustomEvent<'_>) {
|
async fn on_custom_event(&self, _: Room, _: &CustomEvent<'_>) {
|
||||||
self.0.lock().await.push("custom event".to_string())
|
self.0.lock().await.push("custom event".to_string())
|
||||||
}
|
}
|
||||||
|
async fn on_room_notification(&self, _: Room, _: Notification) {
|
||||||
|
self.0.lock().await.push("notification".to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::{identifiers::user_id, Client, Session, SyncSettings};
|
use crate::{identifiers::user_id, Client, Session, SyncSettings};
|
||||||
|
@ -674,7 +692,7 @@ mod test {
|
||||||
async fn get_client() -> Client {
|
async fn get_client() -> Client {
|
||||||
let session = Session {
|
let session = Session {
|
||||||
access_token: "1234".to_owned(),
|
access_token: "1234".to_owned(),
|
||||||
user_id: user_id!("@example:example.com"),
|
user_id: user_id!("@example:localhost"),
|
||||||
device_id: "DEVICEID".into(),
|
device_id: "DEVICEID".into(),
|
||||||
};
|
};
|
||||||
let homeserver = url::Url::parse(&mockito::server_url()).unwrap();
|
let homeserver = url::Url::parse(&mockito::server_url()).unwrap();
|
||||||
|
@ -683,7 +701,7 @@ mod test {
|
||||||
client
|
client
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn mock_sync(client: Client, response: String) {
|
async fn mock_sync(client: &Client, response: String) {
|
||||||
let _m = mock(
|
let _m = mock(
|
||||||
"GET",
|
"GET",
|
||||||
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
||||||
|
@ -705,7 +723,7 @@ mod test {
|
||||||
|
|
||||||
let client = get_client().await;
|
let client = get_client().await;
|
||||||
client.set_event_handler(handler).await;
|
client.set_event_handler(handler).await;
|
||||||
mock_sync(client, test_json::SYNC.to_string()).await;
|
mock_sync(&client, test_json::SYNC.to_string()).await;
|
||||||
|
|
||||||
let v = test_vec.lock().await;
|
let v = test_vec.lock().await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -723,6 +741,7 @@ mod test {
|
||||||
"state member",
|
"state member",
|
||||||
"message",
|
"message",
|
||||||
"presence event",
|
"presence event",
|
||||||
|
"notification",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -735,7 +754,7 @@ mod test {
|
||||||
|
|
||||||
let client = get_client().await;
|
let client = get_client().await;
|
||||||
client.set_event_handler(handler).await;
|
client.set_event_handler(handler).await;
|
||||||
mock_sync(client, test_json::INVITE_SYNC.to_string()).await;
|
mock_sync(&client, test_json::INVITE_SYNC.to_string()).await;
|
||||||
|
|
||||||
let v = test_vec.lock().await;
|
let v = test_vec.lock().await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -756,7 +775,7 @@ mod test {
|
||||||
|
|
||||||
let client = get_client().await;
|
let client = get_client().await;
|
||||||
client.set_event_handler(handler).await;
|
client.set_event_handler(handler).await;
|
||||||
mock_sync(client, test_json::LEAVE_SYNC.to_string()).await;
|
mock_sync(&client, test_json::LEAVE_SYNC.to_string()).await;
|
||||||
|
|
||||||
let v = test_vec.lock().await;
|
let v = test_vec.lock().await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -772,6 +791,7 @@ mod test {
|
||||||
"state member",
|
"state member",
|
||||||
"message",
|
"message",
|
||||||
"presence event",
|
"presence event",
|
||||||
|
"notification",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -784,7 +804,7 @@ mod test {
|
||||||
|
|
||||||
let client = get_client().await;
|
let client = get_client().await;
|
||||||
client.set_event_handler(handler).await;
|
client.set_event_handler(handler).await;
|
||||||
mock_sync(client, test_json::MORE_SYNC.to_string()).await;
|
mock_sync(&client, test_json::MORE_SYNC.to_string()).await;
|
||||||
|
|
||||||
let v = test_vec.lock().await;
|
let v = test_vec.lock().await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -795,6 +815,7 @@ mod test {
|
||||||
"message",
|
"message",
|
||||||
"message", // this is a message edit event
|
"message", // this is a message edit event
|
||||||
"redaction",
|
"redaction",
|
||||||
|
"message", // this is a notice event
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -807,7 +828,7 @@ mod test {
|
||||||
|
|
||||||
let client = get_client().await;
|
let client = get_client().await;
|
||||||
client.set_event_handler(handler).await;
|
client.set_event_handler(handler).await;
|
||||||
mock_sync(client, test_json::VOIP_SYNC.to_string()).await;
|
mock_sync(&client, test_json::VOIP_SYNC.to_string()).await;
|
||||||
|
|
||||||
let v = test_vec.lock().await;
|
let v = test_vec.lock().await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -820,4 +841,45 @@ mod test {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn event_handler_two_syncs() {
|
||||||
|
let vec = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let test_vec = Arc::clone(&vec);
|
||||||
|
let handler = Box::new(EvHandlerTest(vec));
|
||||||
|
|
||||||
|
let client = get_client().await;
|
||||||
|
client.set_event_handler(handler).await;
|
||||||
|
mock_sync(&client, test_json::SYNC.to_string()).await;
|
||||||
|
mock_sync(&client, test_json::MORE_SYNC.to_string()).await;
|
||||||
|
|
||||||
|
let v = test_vec.lock().await;
|
||||||
|
assert_eq!(
|
||||||
|
v.as_slice(),
|
||||||
|
[
|
||||||
|
"receipt event",
|
||||||
|
"account read",
|
||||||
|
"account ignore",
|
||||||
|
"state rules",
|
||||||
|
"state member",
|
||||||
|
"state aliases",
|
||||||
|
"state power",
|
||||||
|
"state canonical",
|
||||||
|
"state member",
|
||||||
|
"state member",
|
||||||
|
"message",
|
||||||
|
"presence event",
|
||||||
|
"notification",
|
||||||
|
"receipt event",
|
||||||
|
"typing event",
|
||||||
|
"message",
|
||||||
|
"message", // this is a message edit event
|
||||||
|
"redaction",
|
||||||
|
"message", // this is a notice event
|
||||||
|
"notification",
|
||||||
|
"notification",
|
||||||
|
"notification",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,26 +20,9 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
result::Result as StdResult,
|
result::Result as StdResult,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
use matrix_sdk_common::{
|
|
||||||
api::r0 as api,
|
|
||||||
deserialized_responses::{
|
|
||||||
AccountData, AmbiguityChanges, Ephemeral, InviteState, InvitedRoom, JoinedRoom, LeftRoom,
|
|
||||||
MemberEvent, MembersResponse, Presence, Rooms, State, StrippedMemberEvent, SyncResponse,
|
|
||||||
Timeline,
|
|
||||||
},
|
|
||||||
events::{
|
|
||||||
presence::PresenceEvent,
|
|
||||||
room::member::{MemberEventContent, MembershipState},
|
|
||||||
AnyBasicEvent, AnyStrippedStateEvent, AnySyncRoomEvent, AnySyncStateEvent,
|
|
||||||
AnyToDeviceEvent, EventContent, StateEvent,
|
|
||||||
},
|
|
||||||
identifiers::{RoomId, UserId},
|
|
||||||
instant::Instant,
|
|
||||||
locks::RwLock,
|
|
||||||
Raw,
|
|
||||||
};
|
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
api::r0::keys::claim_keys::Request as KeysClaimRequest,
|
api::r0::keys::claim_keys::Request as KeysClaimRequest,
|
||||||
|
@ -51,6 +34,25 @@ use matrix_sdk_common::{
|
||||||
locks::Mutex,
|
locks::Mutex,
|
||||||
uuid::Uuid,
|
uuid::Uuid,
|
||||||
};
|
};
|
||||||
|
use matrix_sdk_common::{
|
||||||
|
api::r0::{self as api, push::get_notifications::Notification},
|
||||||
|
deserialized_responses::{
|
||||||
|
AccountData, AmbiguityChanges, Ephemeral, InviteState, InvitedRoom, JoinedRoom, LeftRoom,
|
||||||
|
MemberEvent, MembersResponse, Presence, Rooms, State, StrippedMemberEvent, SyncResponse,
|
||||||
|
Timeline,
|
||||||
|
},
|
||||||
|
events::{
|
||||||
|
presence::PresenceEvent,
|
||||||
|
room::member::{MemberEventContent, MembershipState},
|
||||||
|
AnyBasicEvent, AnyStrippedStateEvent, AnySyncRoomEvent, AnySyncStateEvent,
|
||||||
|
AnyToDeviceEvent, EventContent, EventType, StateEvent,
|
||||||
|
},
|
||||||
|
identifiers::{RoomId, UserId},
|
||||||
|
instant::Instant,
|
||||||
|
locks::RwLock,
|
||||||
|
push::{Action, PushConditionRoomCtx, Ruleset},
|
||||||
|
Raw, UInt,
|
||||||
|
};
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
use matrix_sdk_crypto::{
|
use matrix_sdk_crypto::{
|
||||||
store::{CryptoStore, CryptoStoreError},
|
store::{CryptoStore, CryptoStoreError},
|
||||||
|
@ -413,20 +415,30 @@ impl BaseClient {
|
||||||
self.sync_token.read().await.clone()
|
self.sync_token.read().await.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_timeline(
|
async fn handle_timeline(
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room: &Room,
|
||||||
ruma_timeline: api::sync::sync_events::Timeline,
|
ruma_timeline: api::sync::sync_events::Timeline,
|
||||||
|
push_rules: &Ruleset,
|
||||||
room_info: &mut RoomInfo,
|
room_info: &mut RoomInfo,
|
||||||
changes: &mut StateChanges,
|
changes: &mut StateChanges,
|
||||||
ambiguity_cache: &mut AmbiguityCache,
|
ambiguity_cache: &mut AmbiguityCache,
|
||||||
user_ids: &mut BTreeSet<UserId>,
|
user_ids: &mut BTreeSet<UserId>,
|
||||||
) -> StoreResult<Timeline> {
|
) -> Result<Timeline> {
|
||||||
|
let room_id = room.room_id();
|
||||||
|
let user_id = room.own_user_id();
|
||||||
let mut timeline = Timeline::new(ruma_timeline.limited, ruma_timeline.prev_batch.clone());
|
let mut timeline = Timeline::new(ruma_timeline.limited, ruma_timeline.prev_batch.clone());
|
||||||
|
let mut push_context = self.get_push_room_context(room, room_info, changes).await?;
|
||||||
|
|
||||||
for event in ruma_timeline.events {
|
for event in ruma_timeline.events {
|
||||||
match hoist_room_event_prev_content(&event) {
|
match hoist_room_event_prev_content(&event) {
|
||||||
Ok(mut e) => {
|
Ok(mut e) => {
|
||||||
|
#[cfg(not(feature = "encryption"))]
|
||||||
|
let raw_event = event;
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
let mut raw_event = event;
|
||||||
|
|
||||||
#[allow(clippy::single_match)]
|
#[allow(clippy::single_match)]
|
||||||
match &mut e {
|
match &mut e {
|
||||||
AnySyncRoomEvent::State(s) => match s {
|
AnySyncRoomEvent::State(s) => match s {
|
||||||
|
@ -475,11 +487,14 @@ impl BaseClient {
|
||||||
encrypted,
|
encrypted,
|
||||||
)) => {
|
)) => {
|
||||||
if let Some(olm) = self.olm_machine().await {
|
if let Some(olm) = self.olm_machine().await {
|
||||||
if let Ok(decrypted) =
|
if let Ok(raw_decrypted) =
|
||||||
olm.decrypt_room_event(encrypted, room_id).await
|
olm.decrypt_room_event(encrypted, room_id).await
|
||||||
{
|
{
|
||||||
match decrypted.deserialize() {
|
match raw_decrypted.deserialize() {
|
||||||
Ok(decrypted) => e = decrypted,
|
Ok(decrypted) => {
|
||||||
|
e = decrypted;
|
||||||
|
raw_event = raw_decrypted;
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Error deserializing a decrypted event {:?} ", e)
|
warn!("Error deserializing a decrypted event {:?} ", e)
|
||||||
}
|
}
|
||||||
|
@ -494,6 +509,35 @@ impl BaseClient {
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(context) = &mut push_context {
|
||||||
|
self.update_push_room_context(context, user_id, room_info, changes)
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
push_context = self.get_push_room_context(room, room_info, changes).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(context) = &push_context {
|
||||||
|
let actions = push_rules.get_actions(&raw_event, &context).to_vec();
|
||||||
|
|
||||||
|
if actions.iter().any(|a| matches!(a, Action::Notify)) {
|
||||||
|
changes.add_notification(
|
||||||
|
room_id,
|
||||||
|
Notification::new(
|
||||||
|
actions,
|
||||||
|
raw_event,
|
||||||
|
false,
|
||||||
|
room_id.clone(),
|
||||||
|
SystemTime::now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// TODO if there is an Action::SetTweak(Tweak::Highlight) we need to store
|
||||||
|
// its value with the event so a client can show if the event is highlighted
|
||||||
|
// in the UI.
|
||||||
|
// Requires the possibility to associate custom data with events and to
|
||||||
|
// store them.
|
||||||
|
}
|
||||||
|
|
||||||
timeline.events.push(e);
|
timeline.events.push(e);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -747,6 +791,11 @@ impl BaseClient {
|
||||||
let mut changes = StateChanges::new(next_batch.clone());
|
let mut changes = StateChanges::new(next_batch.clone());
|
||||||
let mut ambiguity_cache = AmbiguityCache::new(self.store.clone());
|
let mut ambiguity_cache = AmbiguityCache::new(self.store.clone());
|
||||||
|
|
||||||
|
self.handle_account_data(account_data.events, &mut changes)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let push_rules = self.get_push_rules(&changes).await?;
|
||||||
|
|
||||||
let mut new_rooms = Rooms::default();
|
let mut new_rooms = Rooms::default();
|
||||||
|
|
||||||
for (room_id, new_info) in rooms.join {
|
for (room_id, new_info) in rooms.join {
|
||||||
|
@ -775,8 +824,9 @@ impl BaseClient {
|
||||||
|
|
||||||
let timeline = self
|
let timeline = self
|
||||||
.handle_timeline(
|
.handle_timeline(
|
||||||
&room_id,
|
&room,
|
||||||
new_info.timeline,
|
new_info.timeline,
|
||||||
|
&push_rules,
|
||||||
&mut room_info,
|
&mut room_info,
|
||||||
&mut changes,
|
&mut changes,
|
||||||
&mut ambiguity_cache,
|
&mut ambiguity_cache,
|
||||||
|
@ -845,8 +895,9 @@ impl BaseClient {
|
||||||
|
|
||||||
let timeline = self
|
let timeline = self
|
||||||
.handle_timeline(
|
.handle_timeline(
|
||||||
&room_id,
|
&room,
|
||||||
new_info.timeline,
|
new_info.timeline,
|
||||||
|
&push_rules,
|
||||||
&mut room_info,
|
&mut room_info,
|
||||||
&mut changes,
|
&mut changes,
|
||||||
&mut ambiguity_cache,
|
&mut ambiguity_cache,
|
||||||
|
@ -903,9 +954,6 @@ impl BaseClient {
|
||||||
|
|
||||||
changes.presence = presence;
|
changes.presence = presence;
|
||||||
|
|
||||||
self.handle_account_data(account_data.events, &mut changes)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
changes.ambiguity_maps = ambiguity_cache.cache;
|
changes.ambiguity_maps = ambiguity_cache.cache;
|
||||||
|
|
||||||
self.store.save_changes(&changes).await?;
|
self.store.save_changes(&changes).await?;
|
||||||
|
@ -932,6 +980,7 @@ impl BaseClient {
|
||||||
ambiguity_changes: AmbiguityChanges {
|
ambiguity_changes: AmbiguityChanges {
|
||||||
changes: ambiguity_cache.changes,
|
changes: ambiguity_cache.changes,
|
||||||
},
|
},
|
||||||
|
notifications: changes.notifications,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
|
@ -1340,6 +1389,129 @@ impl BaseClient {
|
||||||
let olm = self.olm.lock().await;
|
let olm = self.olm.lock().await;
|
||||||
olm.as_ref().cloned()
|
olm.as_ref().cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the push rules.
|
||||||
|
///
|
||||||
|
/// Gets the push rules from `changes` if they have been updated, otherwise get them from the
|
||||||
|
/// store. As a fallback, uses `Ruleset::server_default` if the user is logged in.
|
||||||
|
pub async fn get_push_rules(&self, changes: &StateChanges) -> Result<Ruleset> {
|
||||||
|
if let Some(AnyBasicEvent::PushRules(event)) =
|
||||||
|
changes.account_data.get(&EventType::PushRules.to_string())
|
||||||
|
{
|
||||||
|
Ok(event.content.global.clone())
|
||||||
|
} else if let Some(AnyBasicEvent::PushRules(event)) = self
|
||||||
|
.store
|
||||||
|
.get_account_data_event(EventType::PushRules)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(event.content.global)
|
||||||
|
} else if let Some(session) = self.get_session().await {
|
||||||
|
Ok(Ruleset::server_default(&session.user_id))
|
||||||
|
} else {
|
||||||
|
Ok(Ruleset::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the push context for the given room.
|
||||||
|
///
|
||||||
|
/// Tries to get the data from `changes` or the up to date `room_info`. Loads the data from the
|
||||||
|
/// store otherwise.
|
||||||
|
///
|
||||||
|
/// Returns `None` if some data couldn't be found. This should only happen in brand new rooms,
|
||||||
|
/// while we process its state.
|
||||||
|
pub async fn get_push_room_context(
|
||||||
|
&self,
|
||||||
|
room: &Room,
|
||||||
|
room_info: &RoomInfo,
|
||||||
|
changes: &StateChanges,
|
||||||
|
) -> Result<Option<PushConditionRoomCtx>> {
|
||||||
|
let room_id = room.room_id();
|
||||||
|
let user_id = room.own_user_id();
|
||||||
|
|
||||||
|
let member_count = room_info.active_members_count();
|
||||||
|
|
||||||
|
let user_display_name = if let Some(member) = changes
|
||||||
|
.members
|
||||||
|
.get(room_id)
|
||||||
|
.and_then(|members| members.get(user_id))
|
||||||
|
{
|
||||||
|
member
|
||||||
|
.content
|
||||||
|
.displayname
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| user_id.localpart().to_owned())
|
||||||
|
} else if let Some(member) = room.get_member(user_id).await? {
|
||||||
|
member.name().to_owned()
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let room_power_levels = if let Some(AnySyncStateEvent::RoomPowerLevels(event)) = changes
|
||||||
|
.state
|
||||||
|
.get(room_id)
|
||||||
|
.and_then(|types| types.get(EventType::RoomPowerLevels.as_str()))
|
||||||
|
.and_then(|events| events.get(""))
|
||||||
|
{
|
||||||
|
event.content.clone()
|
||||||
|
} else if let Some(AnySyncStateEvent::RoomPowerLevels(event)) = self
|
||||||
|
.store
|
||||||
|
.get_state_event(room_id, EventType::RoomPowerLevels, "")
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
event.content
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(PushConditionRoomCtx {
|
||||||
|
room_id: room_id.clone(),
|
||||||
|
member_count: UInt::new(member_count).unwrap_or(UInt::MAX),
|
||||||
|
user_display_name,
|
||||||
|
users_power_levels: room_power_levels.users,
|
||||||
|
default_power_level: room_power_levels.users_default,
|
||||||
|
notification_power_levels: room_power_levels.notifications,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the push context for the given room.
|
||||||
|
///
|
||||||
|
/// Updates the context data from `changes` or `room_info`.
|
||||||
|
pub async fn update_push_room_context(
|
||||||
|
&self,
|
||||||
|
push_rules: &mut PushConditionRoomCtx,
|
||||||
|
user_id: &UserId,
|
||||||
|
room_info: &RoomInfo,
|
||||||
|
changes: &StateChanges,
|
||||||
|
) {
|
||||||
|
let room_id = &room_info.room_id;
|
||||||
|
|
||||||
|
push_rules.member_count = UInt::new(room_info.active_members_count()).unwrap_or(UInt::MAX);
|
||||||
|
|
||||||
|
if let Some(member) = changes
|
||||||
|
.members
|
||||||
|
.get(room_id)
|
||||||
|
.and_then(|members| members.get(user_id))
|
||||||
|
{
|
||||||
|
push_rules.user_display_name = member
|
||||||
|
.content
|
||||||
|
.displayname
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| user_id.localpart().to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(AnySyncStateEvent::RoomPowerLevels(event)) = changes
|
||||||
|
.state
|
||||||
|
.get(room_id)
|
||||||
|
.and_then(|types| types.get(EventType::RoomPowerLevels.as_str()))
|
||||||
|
.and_then(|events| events.get(""))
|
||||||
|
{
|
||||||
|
let room_power_levels = event.content.clone();
|
||||||
|
|
||||||
|
push_rules.users_power_levels = room_power_levels.users;
|
||||||
|
push_rules.default_power_level = room_power_levels.users_default;
|
||||||
|
push_rules.notification_power_levels = room_power_levels.notifications;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -533,4 +533,13 @@ impl RoomInfo {
|
||||||
|
|
||||||
changed
|
changed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The number of active members (invited + joined) in the room.
|
||||||
|
///
|
||||||
|
/// The return value is saturated at `u64::MAX`.
|
||||||
|
pub fn active_members_count(&self) -> u64 {
|
||||||
|
self.summary
|
||||||
|
.joined_member_count
|
||||||
|
.saturating_add(self.summary.invited_member_count)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -303,6 +303,13 @@ impl MemoryStore {
|
||||||
#[allow(clippy::map_clone)]
|
#[allow(clippy::map_clone)]
|
||||||
self.stripped_room_info.iter().map(|r| r.clone()).collect()
|
self.stripped_room_info.iter().map(|r| r.clone()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_account_data_event(&self, event_type: EventType) -> Result<Option<AnyBasicEvent>> {
|
||||||
|
Ok(self
|
||||||
|
.account_data
|
||||||
|
.get(event_type.as_ref())
|
||||||
|
.map(|e| e.clone()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
|
@ -385,4 +392,8 @@ impl StateStore for MemoryStore {
|
||||||
.and_then(|d| d.get(display_name).map(|d| d.clone()))
|
.and_then(|d| d.get(display_name).map(|d| d.clone()))
|
||||||
.unwrap_or_default())
|
.unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_account_data_event(&self, event_type: EventType) -> Result<Option<AnyBasicEvent>> {
|
||||||
|
self.get_account_data_event(event_type).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ use std::path::Path;
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
|
api::r0::push::get_notifications::Notification,
|
||||||
async_trait,
|
async_trait,
|
||||||
events::{
|
events::{
|
||||||
presence::PresenceEvent, room::member::MemberEventContent, AnyBasicEvent,
|
presence::PresenceEvent, room::member::MemberEventContent, AnyBasicEvent,
|
||||||
|
@ -185,6 +186,13 @@ pub trait StateStore: AsyncTraitDeps {
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
display_name: &str,
|
display_name: &str,
|
||||||
) -> Result<BTreeSet<UserId>>;
|
) -> Result<BTreeSet<UserId>>;
|
||||||
|
|
||||||
|
/// Get an event out of the account data store.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `event_type` - The event type of the account data event.
|
||||||
|
async fn get_account_data_event(&self, event_type: EventType) -> Result<Option<AnyBasicEvent>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A state store wrapper for the SDK.
|
/// A state store wrapper for the SDK.
|
||||||
|
@ -360,6 +368,9 @@ pub struct StateChanges {
|
||||||
pub stripped_members: BTreeMap<RoomId, BTreeMap<UserId, StrippedMemberEvent>>,
|
pub stripped_members: BTreeMap<RoomId, BTreeMap<UserId, StrippedMemberEvent>>,
|
||||||
/// A map of `RoomId` to `RoomInfo`.
|
/// A map of `RoomId` to `RoomInfo`.
|
||||||
pub invited_room_info: BTreeMap<RoomId, RoomInfo>,
|
pub invited_room_info: BTreeMap<RoomId, RoomInfo>,
|
||||||
|
|
||||||
|
/// A map of `RoomId` to a vector of `Notification`s
|
||||||
|
pub notifications: BTreeMap<RoomId, Vec<Notification>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StateChanges {
|
impl StateChanges {
|
||||||
|
@ -431,4 +442,12 @@ impl StateChanges {
|
||||||
.or_insert_with(BTreeMap::new)
|
.or_insert_with(BTreeMap::new)
|
||||||
.insert(event.state_key().to_string(), event);
|
.insert(event.state_key().to_string(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the `StateChanges` struct with the given room with a new `Notification`.
|
||||||
|
pub fn add_notification(&mut self, room_id: &RoomId, notification: Notification) {
|
||||||
|
self.notifications
|
||||||
|
.entry(room_id.to_owned())
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(notification);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ use matrix_sdk_common::{
|
||||||
events::{
|
events::{
|
||||||
presence::PresenceEvent,
|
presence::PresenceEvent,
|
||||||
room::member::{MemberEventContent, MembershipState},
|
room::member::{MemberEventContent, MembershipState},
|
||||||
AnySyncStateEvent, EventContent, EventType,
|
AnyBasicEvent, AnySyncStateEvent, EventContent, EventType,
|
||||||
},
|
},
|
||||||
identifiers::{RoomId, UserId},
|
identifiers::{RoomId, UserId},
|
||||||
};
|
};
|
||||||
|
@ -134,6 +134,12 @@ impl EncodeKey for (&str, &str, &str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EncodeKey for EventType {
|
||||||
|
fn encode(&self) -> Vec<u8> {
|
||||||
|
self.as_str().encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SledStore {
|
pub struct SledStore {
|
||||||
path: Option<PathBuf>,
|
path: Option<PathBuf>,
|
||||||
|
@ -495,7 +501,7 @@ impl SledStore {
|
||||||
) -> Result<Option<AnySyncStateEvent>> {
|
) -> Result<Option<AnySyncStateEvent>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.room_state
|
.room_state
|
||||||
.get((room_id.as_str(), event_type.to_string().as_str(), state_key).encode())?
|
.get((room_id.as_str(), event_type.as_str(), state_key).encode())?
|
||||||
.map(|e| self.deserialize_event(&e))
|
.map(|e| self.deserialize_event(&e))
|
||||||
.transpose()?)
|
.transpose()?)
|
||||||
}
|
}
|
||||||
|
@ -587,6 +593,17 @@ impl SledStore {
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or_default())
|
.unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_account_data_event(
|
||||||
|
&self,
|
||||||
|
event_type: EventType,
|
||||||
|
) -> Result<Option<AnyBasicEvent>> {
|
||||||
|
Ok(self
|
||||||
|
.account_data
|
||||||
|
.get(event_type.encode())?
|
||||||
|
.map(|m| self.deserialize_event(&m))
|
||||||
|
.transpose()?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -664,6 +681,10 @@ impl StateStore for SledStore {
|
||||||
self.get_users_with_display_name(room_id, display_name)
|
self.get_users_with_display_name(room_id, display_name)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_account_data_event(&self, event_type: EventType) -> Result<Option<AnyBasicEvent>> {
|
||||||
|
self.get_account_data_event(event_type).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -2,9 +2,12 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::BTreeMap, convert::TryFrom, time::SystemTime};
|
use std::{collections::BTreeMap, convert::TryFrom, time::SystemTime};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
api::r0::sync::sync_events::{
|
api::r0::{
|
||||||
|
push::get_notifications::Notification,
|
||||||
|
sync::sync_events::{
|
||||||
DeviceLists, UnreadNotificationsCount as RumaUnreadNotificationsCount,
|
DeviceLists, UnreadNotificationsCount as RumaUnreadNotificationsCount,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
events::{
|
events::{
|
||||||
presence::PresenceEvent, room::member::MemberEventContent, AnyBasicEvent,
|
presence::PresenceEvent, room::member::MemberEventContent, AnyBasicEvent,
|
||||||
AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncRoomEvent, AnySyncStateEvent,
|
AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncRoomEvent, AnySyncStateEvent,
|
||||||
|
@ -55,6 +58,8 @@ pub struct SyncResponse {
|
||||||
pub device_one_time_keys_count: BTreeMap<DeviceKeyAlgorithm, u64>,
|
pub device_one_time_keys_count: BTreeMap<DeviceKeyAlgorithm, u64>,
|
||||||
/// Collection of ambiguioty changes that room member events trigger.
|
/// Collection of ambiguioty changes that room member events trigger.
|
||||||
pub ambiguity_changes: AmbiguityChanges,
|
pub ambiguity_changes: AmbiguityChanges,
|
||||||
|
/// New notifications per room.
|
||||||
|
pub notifications: BTreeMap<RoomId, Vec<Notification>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyncResponse {
|
impl SyncResponse {
|
||||||
|
|
|
@ -543,7 +543,7 @@ lazy_static! {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref MORE_SYNC: JsonValue = json!({
|
pub static ref MORE_SYNC: JsonValue = json!({
|
||||||
"device_one_time_keys_count": {},
|
"device_one_time_keys_count": {},
|
||||||
"next_batch": "s526_47314_0_7_1_1_1_11444_1",
|
"next_batch": "s526_47314_0_7_1_1_1_11444_2",
|
||||||
"device_lists": {
|
"device_lists": {
|
||||||
"changed": [
|
"changed": [
|
||||||
"@example:example.org"
|
"@example:example.org"
|
||||||
|
@ -676,7 +676,22 @@ lazy_static! {
|
||||||
"unsigned": {
|
"unsigned": {
|
||||||
"age": 85
|
"age": 85
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "This is a notice",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "<em>This is a notice</em>",
|
||||||
|
"msgtype": "m.notice"
|
||||||
|
},
|
||||||
|
"event_id": "$098237280074GZeOm:localhost",
|
||||||
|
"origin_server_ts": 162037280,
|
||||||
|
"sender": "@bot:localhost",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 25
|
||||||
}
|
}
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"limited": true,
|
"limited": true,
|
||||||
"prev_batch": "t392-516_47314_0_7_1_1_1_11444_1"
|
"prev_batch": "t392-516_47314_0_7_1_1_1_11444_1"
|
||||||
|
|
Loading…
Reference in New Issue