matrix_sdk_base/
sync.rs

1// Copyright 2022 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The SDK's representation of the result of a `/sync` request.
16
17use std::{collections::BTreeMap, fmt};
18
19use matrix_sdk_common::{debug::DebugRawEvent, deserialized_responses::TimelineEvent};
20use ruma::{
21    api::client::sync::sync_events::{
22        v3::{InvitedRoom as InvitedRoomUpdate, KnockedRoom as KnockedRoomUpdate},
23        UnreadNotificationsCount as RumaUnreadNotificationsCount,
24    },
25    events::{
26        presence::PresenceEvent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
27        AnySyncEphemeralRoomEvent, AnySyncStateEvent, AnyToDeviceEvent,
28    },
29    push::Action,
30    serde::Raw,
31    OwnedEventId, OwnedRoomId,
32};
33use serde::{Deserialize, Serialize};
34
35use crate::{
36    debug::{DebugInvitedRoom, DebugKnockedRoom, DebugListOfRawEvents, DebugListOfRawEventsNoId},
37    deserialized_responses::{AmbiguityChange, RawAnySyncOrStrippedTimelineEvent},
38    store::Store,
39};
40
41/// Generalized representation of a `/sync` response.
42///
43/// This type is intended to be applicable regardless of the endpoint used for
44/// syncing.
45#[derive(Clone, Default)]
46pub struct SyncResponse {
47    /// Updates to rooms.
48    pub rooms: RoomUpdates,
49    /// Updates to the presence status of other users.
50    pub presence: Vec<Raw<PresenceEvent>>,
51    /// The global private data created by this user.
52    pub account_data: Vec<Raw<AnyGlobalAccountDataEvent>>,
53    /// Messages sent directly between devices.
54    pub to_device: Vec<Raw<AnyToDeviceEvent>>,
55    /// New notifications per room.
56    pub notifications: BTreeMap<OwnedRoomId, Vec<Notification>>,
57}
58
59#[cfg(not(tarpaulin_include))]
60impl fmt::Debug for SyncResponse {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        f.debug_struct("SyncResponse")
63            .field("rooms", &self.rooms)
64            .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
65            .field("to_device", &DebugListOfRawEventsNoId(&self.to_device))
66            .field("notifications", &self.notifications)
67            .finish_non_exhaustive()
68    }
69}
70
71/// Updates to rooms in a [`SyncResponse`].
72#[derive(Clone, Default)]
73pub struct RoomUpdates {
74    /// The rooms that the user has left or been banned from.
75    pub leave: BTreeMap<OwnedRoomId, LeftRoomUpdate>,
76    /// The rooms that the user has joined.
77    pub join: BTreeMap<OwnedRoomId, JoinedRoomUpdate>,
78    /// The rooms that the user has been invited to.
79    pub invite: BTreeMap<OwnedRoomId, InvitedRoomUpdate>,
80    /// The rooms that the user has knocked on.
81    pub knocked: BTreeMap<OwnedRoomId, KnockedRoomUpdate>,
82}
83
84impl RoomUpdates {
85    /// Update the caches for the rooms that received updates.
86    ///
87    /// This will only fill the in-memory caches, not save the info on disk.
88    pub(crate) async fn update_in_memory_caches(&self, store: &Store) {
89        for room in self
90            .leave
91            .keys()
92            .chain(self.join.keys())
93            .chain(self.invite.keys())
94            .chain(self.knocked.keys())
95            .filter_map(|room_id| store.room(room_id))
96        {
97            let _ = room.compute_display_name().await;
98        }
99    }
100}
101
102#[cfg(not(tarpaulin_include))]
103impl fmt::Debug for RoomUpdates {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        f.debug_struct("RoomUpdates")
106            .field("leave", &self.leave)
107            .field("join", &self.join)
108            .field("invite", &DebugInvitedRoomUpdates(&self.invite))
109            .field("knocked", &DebugKnockedRoomUpdates(&self.knocked))
110            .finish()
111    }
112}
113
114/// Updates to joined rooms.
115#[derive(Clone, Default)]
116pub struct JoinedRoomUpdate {
117    /// Counts of unread notifications for this room.
118    pub unread_notifications: UnreadNotificationsCount,
119    /// The timeline of messages and state changes in the room.
120    pub timeline: Timeline,
121    /// Updates to the state, between the time indicated by the `since`
122    /// parameter, and the start of the `timeline` (or all state up to the
123    /// start of the `timeline`, if `since` is not given, or `full_state` is
124    /// true).
125    pub state: Vec<Raw<AnySyncStateEvent>>,
126    /// The private data that this user has attached to this room.
127    pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
128    /// The ephemeral events in the room that aren't recorded in the timeline or
129    /// state of the room. e.g. typing.
130    pub ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
131    /// Collection of ambiguity changes that room member events trigger.
132    ///
133    /// This is a map of event ID of the `m.room.member` event to the
134    /// details of the ambiguity change.
135    pub ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
136}
137
138#[cfg(not(tarpaulin_include))]
139impl fmt::Debug for JoinedRoomUpdate {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        f.debug_struct("JoinedRoomUpdate")
142            .field("unread_notifications", &self.unread_notifications)
143            .field("timeline", &self.timeline)
144            .field("state", &DebugListOfRawEvents(&self.state))
145            .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
146            .field("ephemeral", &self.ephemeral)
147            .field("ambiguity_changes", &self.ambiguity_changes)
148            .finish()
149    }
150}
151
152impl JoinedRoomUpdate {
153    pub(crate) fn new(
154        timeline: Timeline,
155        state: Vec<Raw<AnySyncStateEvent>>,
156        account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
157        ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
158        unread_notifications: UnreadNotificationsCount,
159        ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
160    ) -> Self {
161        Self { unread_notifications, timeline, state, account_data, ephemeral, ambiguity_changes }
162    }
163}
164
165/// Counts of unread notifications for a room.
166#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
167pub struct UnreadNotificationsCount {
168    /// The number of unread notifications for this room with the highlight flag
169    /// set.
170    pub highlight_count: u64,
171    /// The total number of unread notifications for this room.
172    pub notification_count: u64,
173}
174
175impl From<RumaUnreadNotificationsCount> for UnreadNotificationsCount {
176    fn from(notifications: RumaUnreadNotificationsCount) -> Self {
177        Self {
178            highlight_count: notifications.highlight_count.map(|c| c.into()).unwrap_or(0),
179            notification_count: notifications.notification_count.map(|c| c.into()).unwrap_or(0),
180        }
181    }
182}
183
184/// Updates to left rooms.
185#[derive(Clone, Default)]
186pub struct LeftRoomUpdate {
187    /// The timeline of messages and state changes in the room up to the point
188    /// when the user left.
189    pub timeline: Timeline,
190    /// Updates to the state, between the time indicated by the `since`
191    /// parameter, and the start of the `timeline` (or all state up to the
192    /// start of the `timeline`, if `since` is not given, or `full_state` is
193    /// true).
194    pub state: Vec<Raw<AnySyncStateEvent>>,
195    /// The private data that this user has attached to this room.
196    pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
197    /// Collection of ambiguity changes that room member events trigger.
198    ///
199    /// This is a map of event ID of the `m.room.member` event to the
200    /// details of the ambiguity change.
201    pub ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
202}
203
204impl LeftRoomUpdate {
205    pub(crate) fn new(
206        timeline: Timeline,
207        state: Vec<Raw<AnySyncStateEvent>>,
208        account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
209        ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
210    ) -> Self {
211        Self { timeline, state, account_data, ambiguity_changes }
212    }
213}
214
215#[cfg(not(tarpaulin_include))]
216impl fmt::Debug for LeftRoomUpdate {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        f.debug_struct("LeftRoomUpdate")
219            .field("timeline", &self.timeline)
220            .field("state", &DebugListOfRawEvents(&self.state))
221            .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
222            .field("ambiguity_changes", &self.ambiguity_changes)
223            .finish()
224    }
225}
226
227/// Events in the room.
228#[derive(Clone, Debug, Default)]
229pub struct Timeline {
230    /// True if the number of events returned was limited by the `limit` on the
231    /// filter.
232    pub limited: bool,
233
234    /// A token that can be supplied to to the `from` parameter of the
235    /// `/rooms/{roomId}/messages` endpoint.
236    pub prev_batch: Option<String>,
237
238    /// A list of events.
239    pub events: Vec<TimelineEvent>,
240}
241
242impl Timeline {
243    pub(crate) fn new(limited: bool, prev_batch: Option<String>) -> Self {
244        Self { limited, prev_batch, ..Default::default() }
245    }
246}
247
248struct DebugInvitedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, InvitedRoomUpdate>);
249
250#[cfg(not(tarpaulin_include))]
251impl fmt::Debug for DebugInvitedRoomUpdates<'_> {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        f.debug_map().entries(self.0.iter().map(|(k, v)| (k, DebugInvitedRoom(v)))).finish()
254    }
255}
256
257struct DebugKnockedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, KnockedRoomUpdate>);
258
259#[cfg(not(tarpaulin_include))]
260impl fmt::Debug for DebugKnockedRoomUpdates<'_> {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        f.debug_map().entries(self.0.iter().map(|(k, v)| (k, DebugKnockedRoom(v)))).finish()
263    }
264}
265
266/// A notification triggered by a sync response.
267#[derive(Clone)]
268pub struct Notification {
269    /// The actions to perform when the conditions for this rule are met.
270    pub actions: Vec<Action>,
271
272    /// The event that triggered the notification.
273    pub event: RawAnySyncOrStrippedTimelineEvent,
274}
275
276#[cfg(not(tarpaulin_include))]
277impl fmt::Debug for Notification {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        let event_debug = match &self.event {
280            RawAnySyncOrStrippedTimelineEvent::Sync(ev) => DebugRawEvent(ev),
281            RawAnySyncOrStrippedTimelineEvent::Stripped(ev) => DebugRawEvent(ev.cast_ref()),
282        };
283
284        f.debug_struct("Notification")
285            .field("actions", &self.actions)
286            .field("event", &event_debug)
287            .finish()
288    }
289}