use std::{collections::BTreeMap, fmt};
use matrix_sdk_common::{
debug::DebugRawEvent,
deserialized_responses::{ProcessedToDeviceEvent, TimelineEvent},
};
pub use ruma::api::client::sync::sync_events::v3::{
InvitedRoom as InvitedRoomUpdate, KnockedRoom as KnockedRoomUpdate,
};
use ruma::{
OwnedEventId, OwnedRoomId,
api::client::sync::sync_events::UnreadNotificationsCount as RumaUnreadNotificationsCount,
events::{
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncEphemeralRoomEvent,
AnySyncStateEvent, presence::PresenceEvent,
},
push::Action,
serde::Raw,
};
use serde::{Deserialize, Serialize};
use crate::{
debug::{
DebugInvitedRoom, DebugKnockedRoom, DebugListOfProcessedToDeviceEvents,
DebugListOfRawEvents, DebugListOfRawEventsNoId,
},
deserialized_responses::{AmbiguityChange, RawAnySyncOrStrippedTimelineEvent},
};
#[derive(Clone, Default)]
pub struct SyncResponse {
pub rooms: RoomUpdates,
pub presence: Vec<Raw<PresenceEvent>>,
pub account_data: Vec<Raw<AnyGlobalAccountDataEvent>>,
pub to_device: Vec<ProcessedToDeviceEvent>,
pub notifications: BTreeMap<OwnedRoomId, Vec<Notification>>,
}
#[cfg(not(tarpaulin_include))]
impl fmt::Debug for SyncResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SyncResponse")
.field("rooms", &self.rooms)
.field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
.field("to_device", &DebugListOfProcessedToDeviceEvents(&self.to_device))
.field("notifications", &self.notifications)
.finish_non_exhaustive()
}
}
#[derive(Clone, Default)]
pub struct RoomUpdates {
pub left: BTreeMap<OwnedRoomId, LeftRoomUpdate>,
pub joined: BTreeMap<OwnedRoomId, JoinedRoomUpdate>,
pub invited: BTreeMap<OwnedRoomId, InvitedRoomUpdate>,
pub knocked: BTreeMap<OwnedRoomId, KnockedRoomUpdate>,
}
impl RoomUpdates {
pub fn iter_all_room_ids(&self) -> impl Iterator<Item = &OwnedRoomId> {
self.left
.keys()
.chain(self.joined.keys())
.chain(self.invited.keys())
.chain(self.knocked.keys())
}
pub fn is_empty(&self) -> bool {
self.invited.is_empty()
&& self.joined.is_empty()
&& self.knocked.is_empty()
&& self.left.is_empty()
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use assert_matches::assert_matches;
use ruma::room_id;
use super::{
InvitedRoomUpdate, JoinedRoomUpdate, KnockedRoomUpdate, LeftRoomUpdate, RoomUpdates,
};
#[test]
fn test_room_updates_iter_all_room_ids() {
let room_id_0 = room_id!("!r0");
let room_id_1 = room_id!("!r1");
let room_id_2 = room_id!("!r2");
let room_id_3 = room_id!("!r3");
let room_id_4 = room_id!("!r4");
let room_id_5 = room_id!("!r5");
let room_id_6 = room_id!("!r6");
let room_id_7 = room_id!("!r7");
let room_updates = RoomUpdates {
left: {
let mut left = BTreeMap::new();
left.insert(room_id_0.to_owned(), LeftRoomUpdate::default());
left.insert(room_id_1.to_owned(), LeftRoomUpdate::default());
left
},
joined: {
let mut joined = BTreeMap::new();
joined.insert(room_id_2.to_owned(), JoinedRoomUpdate::default());
joined.insert(room_id_3.to_owned(), JoinedRoomUpdate::default());
joined
},
invited: {
let mut invited = BTreeMap::new();
invited.insert(room_id_4.to_owned(), InvitedRoomUpdate::default());
invited.insert(room_id_5.to_owned(), InvitedRoomUpdate::default());
invited
},
knocked: {
let mut knocked = BTreeMap::new();
knocked.insert(room_id_6.to_owned(), KnockedRoomUpdate::default());
knocked.insert(room_id_7.to_owned(), KnockedRoomUpdate::default());
knocked
},
};
let mut iter = room_updates.iter_all_room_ids();
assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_0));
assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_1));
assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_2));
assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_3));
assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_4));
assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_5));
assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_6));
assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_7));
assert!(iter.next().is_none());
}
#[test]
fn test_empty_room_updates() {
let room_updates = RoomUpdates::default();
let mut iter = room_updates.iter_all_room_ids();
assert!(iter.next().is_none());
assert!(room_updates.is_empty());
}
}
#[cfg(not(tarpaulin_include))]
impl fmt::Debug for RoomUpdates {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RoomUpdates")
.field("left", &self.left)
.field("joined", &self.joined)
.field("invited", &DebugInvitedRoomUpdates(&self.invited))
.field("knocked", &DebugKnockedRoomUpdates(&self.knocked))
.finish()
}
}
#[derive(Clone, Default)]
pub struct JoinedRoomUpdate {
pub unread_notifications: UnreadNotificationsCount,
pub timeline: Timeline,
pub state: State,
pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
pub ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
pub ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
}
#[cfg(not(tarpaulin_include))]
impl fmt::Debug for JoinedRoomUpdate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JoinedRoomUpdate")
.field("unread_notifications", &self.unread_notifications)
.field("timeline", &self.timeline)
.field("state", &self.state)
.field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
.field("ephemeral", &self.ephemeral)
.field("ambiguity_changes", &self.ambiguity_changes)
.finish()
}
}
impl JoinedRoomUpdate {
pub(crate) fn new(
timeline: Timeline,
state: State,
account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
unread_notifications: UnreadNotificationsCount,
ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
) -> Self {
Self { unread_notifications, timeline, state, account_data, ephemeral, ambiguity_changes }
}
}
#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub struct UnreadNotificationsCount {
pub highlight_count: u64,
pub notification_count: u64,
}
impl From<RumaUnreadNotificationsCount> for UnreadNotificationsCount {
fn from(notifications: RumaUnreadNotificationsCount) -> Self {
Self {
highlight_count: notifications.highlight_count.map(|c| c.into()).unwrap_or(0),
notification_count: notifications.notification_count.map(|c| c.into()).unwrap_or(0),
}
}
}
#[derive(Clone, Default)]
pub struct LeftRoomUpdate {
pub timeline: Timeline,
pub state: State,
pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
pub ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
}
impl LeftRoomUpdate {
pub(crate) fn new(
timeline: Timeline,
state: State,
account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
) -> Self {
Self { timeline, state, account_data, ambiguity_changes }
}
}
#[cfg(not(tarpaulin_include))]
impl fmt::Debug for LeftRoomUpdate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LeftRoomUpdate")
.field("timeline", &self.timeline)
.field("state", &self.state)
.field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
.field("ambiguity_changes", &self.ambiguity_changes)
.finish()
}
}
#[derive(Clone, Debug, Default)]
pub struct Timeline {
pub limited: bool,
pub prev_batch: Option<String>,
pub events: Vec<TimelineEvent>,
}
impl Timeline {
pub(crate) fn new(limited: bool, prev_batch: Option<String>) -> Self {
Self { limited, prev_batch, ..Default::default() }
}
}
#[derive(Clone)]
pub enum State {
Before(Vec<Raw<AnySyncStateEvent>>),
After(Vec<Raw<AnySyncStateEvent>>),
}
impl Default for State {
fn default() -> Self {
Self::Before(vec![])
}
}
#[cfg(not(tarpaulin_include))]
impl fmt::Debug for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Before(events) => {
f.debug_tuple("Before").field(&DebugListOfRawEvents(events)).finish()
}
Self::After(events) => {
f.debug_tuple("After").field(&DebugListOfRawEvents(events)).finish()
}
}
}
}
struct DebugInvitedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, InvitedRoomUpdate>);
#[cfg(not(tarpaulin_include))]
impl fmt::Debug for DebugInvitedRoomUpdates<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.0.iter().map(|(k, v)| (k, DebugInvitedRoom(v)))).finish()
}
}
struct DebugKnockedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, KnockedRoomUpdate>);
#[cfg(not(tarpaulin_include))]
impl fmt::Debug for DebugKnockedRoomUpdates<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.0.iter().map(|(k, v)| (k, DebugKnockedRoom(v)))).finish()
}
}
#[derive(Clone)]
pub struct Notification {
pub actions: Vec<Action>,
pub event: RawAnySyncOrStrippedTimelineEvent,
}
#[cfg(not(tarpaulin_include))]
impl fmt::Debug for Notification {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let event_debug = match &self.event {
RawAnySyncOrStrippedTimelineEvent::Sync(ev) => DebugRawEvent(ev),
RawAnySyncOrStrippedTimelineEvent::Stripped(ev) => {
DebugRawEvent(ev.cast_ref_unchecked())
}
};
f.debug_struct("Notification")
.field("actions", &self.actions)
.field("event", &event_debug)
.finish()
}
}