atagen

matrix-rust-sdk

public
Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk.git
crates/matrix-sdk-base/src/sync.rsRust432 lines15.7 KB
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
17
use std::{collections::BTreeMap, fmt};
18
19
use matrix_sdk_common::{
20
    debug::DebugRawEvent,
21
    deserialized_responses::{ProcessedToDeviceEvent, TimelineEvent},
22
};
23
pub use ruma::api::client::sync::sync_events::v3::{
24
    InvitedRoom as InvitedRoomUpdate, KnockedRoom as KnockedRoomUpdate,
25
};
26
use ruma::{
27
    OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId,
28
    api::client::sync::sync_events::UnreadNotificationsCount as RumaUnreadNotificationsCount,
29
    events::{
30
        AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncEphemeralRoomEvent,
31
        AnySyncStateEvent, presence::PresenceEvent,
32
    },
33
    push::Action,
34
    serde::Raw,
35
};
36
use serde::{Deserialize, Serialize};
37
38
use crate::{
39
    debug::{
40
        DebugInvitedRoom, DebugKnockedRoom, DebugListOfProcessedToDeviceEvents,
41
        DebugListOfRawEvents, DebugListOfRawEventsNoId,
42
    },
43
    deserialized_responses::{AmbiguityChange, RawAnySyncOrStrippedTimelineEvent},
44
};
45
46
/// Generalized representation of a `/sync` response.
47
///
48
/// This type is intended to be applicable regardless of the endpoint used for
49
/// syncing.
50
#[derive(Clone, Default)]
51
pub struct SyncResponse {
52
    /// Updates to rooms.
53
    pub rooms: RoomUpdates,
54
    /// Updates to the presence status of other users.
55
    pub presence: Vec<Raw<PresenceEvent>>,
56
    /// The global private data created by this user.
57
    pub account_data: Vec<Raw<AnyGlobalAccountDataEvent>>,
58
    /// Messages sent directly between devices.
59
    pub to_device: Vec<ProcessedToDeviceEvent>,
60
    /// New notifications per room.
61
    pub notifications: BTreeMap<OwnedRoomId, Vec<Notification>>,
62
}
63
64
#[cfg(not(tarpaulin_include))]
65
impl fmt::Debug for SyncResponse {
66
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67
        f.debug_struct("SyncResponse")
68
            .field("rooms", &self.rooms)
69
            .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
70
            .field("to_device", &DebugListOfProcessedToDeviceEvents(&self.to_device))
71
            .field("notifications", &self.notifications)
72
            .finish_non_exhaustive()
73
    }
74
}
75
76
/// Updates to rooms in a [`SyncResponse`].
77
#[derive(Clone, Default)]
78
pub struct RoomUpdates {
79
    /// The rooms that the user has left or been banned from.
80
    pub left: BTreeMap<OwnedRoomId, LeftRoomUpdate>,
81
    /// The rooms that the user has joined.
82
    pub joined: BTreeMap<OwnedRoomId, JoinedRoomUpdate>,
83
    /// The rooms that the user has been invited to.
84
    pub invited: BTreeMap<OwnedRoomId, InvitedRoomUpdate>,
85
    /// The rooms that the user has knocked on.
86
    pub knocked: BTreeMap<OwnedRoomId, KnockedRoomUpdate>,
87
}
88
89
impl RoomUpdates {
90
    /// Iterate over all room IDs, from [`RoomUpdates::left`],
91
    /// [`RoomUpdates::joined`], [`RoomUpdates::invited`] and
92
    /// [`RoomUpdates::knocked`].
93
    pub fn iter_all_room_ids(&self) -> impl Iterator<Item = &OwnedRoomId> {
94
        self.left
95
            .keys()
96
            .chain(self.joined.keys())
97
            .chain(self.invited.keys())
98
            .chain(self.knocked.keys())
99
    }
100
101
    /// Returns whether or not this update contains any changes to the list
102
    /// of invited, joined, knocked or left rooms.
103
    pub fn is_empty(&self) -> bool {
104
        self.invited.is_empty()
105
            && self.joined.is_empty()
106
            && self.knocked.is_empty()
107
            && self.left.is_empty()
108
    }
109
}
110
111
#[cfg(test)]
112
mod tests {
113
    use std::collections::BTreeMap;
114
115
    use assert_matches::assert_matches;
116
    use ruma::room_id;
117
118
    use super::{
119
        InvitedRoomUpdate, JoinedRoomUpdate, KnockedRoomUpdate, LeftRoomUpdate, RoomUpdates,
120
    };
121
122
    #[test]
123
    fn test_room_updates_iter_all_room_ids() {
124
        let room_id_0 = room_id!("!r0");
125
        let room_id_1 = room_id!("!r1");
126
        let room_id_2 = room_id!("!r2");
127
        let room_id_3 = room_id!("!r3");
128
        let room_id_4 = room_id!("!r4");
129
        let room_id_5 = room_id!("!r5");
130
        let room_id_6 = room_id!("!r6");
131
        let room_id_7 = room_id!("!r7");
132
        let room_updates = RoomUpdates {
133
            left: {
134
                let mut left = BTreeMap::new();
135
                left.insert(room_id_0.to_owned(), LeftRoomUpdate::default());
136
                left.insert(room_id_1.to_owned(), LeftRoomUpdate::default());
137
                left
138
            },
139
            joined: {
140
                let mut joined = BTreeMap::new();
141
                joined.insert(room_id_2.to_owned(), JoinedRoomUpdate::default());
142
                joined.insert(room_id_3.to_owned(), JoinedRoomUpdate::default());
143
                joined
144
            },
145
            invited: {
146
                let mut invited = BTreeMap::new();
147
                invited.insert(room_id_4.to_owned(), InvitedRoomUpdate::default());
148
                invited.insert(room_id_5.to_owned(), InvitedRoomUpdate::default());
149
                invited
150
            },
151
            knocked: {
152
                let mut knocked = BTreeMap::new();
153
                knocked.insert(room_id_6.to_owned(), KnockedRoomUpdate::default());
154
                knocked.insert(room_id_7.to_owned(), KnockedRoomUpdate::default());
155
                knocked
156
            },
157
        };
158
159
        let mut iter = room_updates.iter_all_room_ids();
160
        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_0));
161
        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_1));
162
        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_2));
163
        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_3));
164
        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_4));
165
        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_5));
166
        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_6));
167
        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_7));
168
        assert!(iter.next().is_none());
169
    }
170
171
    #[test]
172
    fn test_empty_room_updates() {
173
        let room_updates = RoomUpdates::default();
174
175
        let mut iter = room_updates.iter_all_room_ids();
176
        assert!(iter.next().is_none());
177
178
        assert!(room_updates.is_empty());
179
    }
180
}
181
182
#[cfg(not(tarpaulin_include))]
183
impl fmt::Debug for RoomUpdates {
184
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185
        f.debug_struct("RoomUpdates")
186
            .field("left", &self.left)
187
            .field("joined", &self.joined)
188
            .field("invited", &DebugInvitedRoomUpdates(&self.invited))
189
            .field("knocked", &DebugKnockedRoomUpdates(&self.knocked))
190
            .finish()
191
    }
192
}
193
194
/// Updates to joined rooms.
195
#[derive(Clone, Default)]
196
pub struct JoinedRoomUpdate {
197
    /// Counts of unread notifications for this room.
198
    pub unread_notifications: UnreadNotificationsCount,
199
    /// The timeline of messages and state changes in the room.
200
    pub timeline: Timeline,
201
    /// Updates to the state.
202
    ///
203
    /// If `since` is missing or `full_state` is true, the start point of the
204
    /// update is the beginning of the timeline. Otherwise, the start point
205
    /// is the time specified in `since`.
206
    ///
207
    /// If `state_after` was used, the end point of the update is the end of the
208
    /// `timeline`. Otherwise, the end point of these updates is the start of
209
    /// the `timeline`, and to calculate room state we must scan the `timeline`
210
    /// for state events as well as using this information in this property.
211
    pub state: State,
212
    /// The private data that this user has attached to this room.
213
    pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
214
    /// The ephemeral events in the room that aren't recorded in the timeline or
215
    /// state of the room. e.g. typing.
216
    pub ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
217
    /// Collection of ambiguity changes that room member events trigger.
218
    ///
219
    /// This is a map of event ID of the `m.room.member` event to the
220
    /// details of the ambiguity change.
221
    pub ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
222
    /// Collection of avatar changes that room member events trigger.
223
    pub avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
224
}
225
226
#[cfg(not(tarpaulin_include))]
227
impl fmt::Debug for JoinedRoomUpdate {
228
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229
        f.debug_struct("JoinedRoomUpdate")
230
            .field("unread_notifications", &self.unread_notifications)
231
            .field("timeline", &self.timeline)
232
            .field("state", &self.state)
233
            .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
234
            .field("ephemeral", &self.ephemeral)
235
            .field("ambiguity_changes", &self.ambiguity_changes)
236
            .finish()
237
    }
238
}
239
240
impl JoinedRoomUpdate {
241
    pub(crate) fn new(
242
        timeline: Timeline,
243
        state: State,
244
        account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
245
        ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
246
        unread_notifications: UnreadNotificationsCount,
247
        ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
248
        avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
249
    ) -> Self {
250
        Self {
251
            unread_notifications,
252
            timeline,
253
            state,
254
            account_data,
255
            ephemeral,
256
            ambiguity_changes,
257
            avatar_changes,
258
        }
259
    }
260
}
261
262
/// Counts of unread notifications for a room.
263
#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
264
pub struct UnreadNotificationsCount {
265
    /// The number of unread notifications for this room with the highlight flag
266
    /// set.
267
    pub highlight_count: u64,
268
    /// The total number of unread notifications for this room.
269
    pub notification_count: u64,
270
}
271
272
impl From<RumaUnreadNotificationsCount> for UnreadNotificationsCount {
273
    fn from(notifications: RumaUnreadNotificationsCount) -> Self {
274
        Self {
275
            highlight_count: notifications.highlight_count.map(|c| c.into()).unwrap_or(0),
276
            notification_count: notifications.notification_count.map(|c| c.into()).unwrap_or(0),
277
        }
278
    }
279
}
280
281
/// Updates to left rooms.
282
#[derive(Clone, Default)]
283
pub struct LeftRoomUpdate {
284
    /// The timeline of messages and state changes in the room up to the point
285
    /// when the user left.
286
    pub timeline: Timeline,
287
    /// Updates to the state.
288
    ///
289
    /// If `since` is missing or `full_state` is true, the start point of the
290
    /// update is the beginning of the timeline. Otherwise, the start point
291
    /// is the time specified in `since`.
292
    ///
293
    /// If `state_after` was used, the end point of the update is the end of the
294
    /// `timeline`. Otherwise, the end point of these updates is the start of
295
    /// the `timeline`, and to calculate room state we must scan the `timeline`
296
    /// for state events as well as using this information in this property.
297
    pub state: State,
298
    /// The private data that this user has attached to this room.
299
    pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
300
    /// Collection of ambiguity changes that room member events trigger.
301
    ///
302
    /// This is a map of event ID of the `m.room.member` event to the
303
    /// details of the ambiguity change.
304
    pub ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
305
}
306
307
impl LeftRoomUpdate {
308
    pub(crate) fn new(
309
        timeline: Timeline,
310
        state: State,
311
        account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
312
        ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
313
    ) -> Self {
314
        Self { timeline, state, account_data, ambiguity_changes }
315
    }
316
}
317
318
#[cfg(not(tarpaulin_include))]
319
impl fmt::Debug for LeftRoomUpdate {
320
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321
        f.debug_struct("LeftRoomUpdate")
322
            .field("timeline", &self.timeline)
323
            .field("state", &self.state)
324
            .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
325
            .field("ambiguity_changes", &self.ambiguity_changes)
326
            .finish()
327
    }
328
}
329
330
/// Events in the room.
331
#[derive(Clone, Debug, Default)]
332
pub struct Timeline {
333
    /// True if the number of events returned was limited by the `limit` on the
334
    /// filter.
335
    pub limited: bool,
336
337
    /// A token that can be supplied to to the `from` parameter of the
338
    /// `/rooms/{roomId}/messages` endpoint.
339
    pub prev_batch: Option<String>,
340
341
    /// A list of events.
342
    pub events: Vec<TimelineEvent>,
343
}
344
345
impl Timeline {
346
    pub(crate) fn new(limited: bool, prev_batch: Option<String>) -> Self {
347
        Self { limited, prev_batch, ..Default::default() }
348
    }
349
}
350
351
/// State changes in the room.
352
#[derive(Clone)]
353
pub enum State {
354
    /// The state changes between the previous sync and the start of the
355
    /// timeline.
356
    ///
357
    /// To get the full list of state changes since the previous sync, the state
358
    /// events in [`Timeline`] must be added to these events to update the local
359
    /// state.
360
    Before(Vec<Raw<AnySyncStateEvent>>),
361
362
    /// The state changes between the previous sync and the end of the timeline.
363
    ///
364
    /// This contains the full list of state changes since the previous sync.
365
    /// State events in [`Timeline`] must be ignored to update the local state.
366
    After(Vec<Raw<AnySyncStateEvent>>),
367
}
368
369
impl Default for State {
370
    fn default() -> Self {
371
        Self::Before(vec![])
372
    }
373
}
374
375
#[cfg(not(tarpaulin_include))]
376
impl fmt::Debug for State {
377
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378
        match self {
379
            Self::Before(events) => {
380
                f.debug_tuple("Before").field(&DebugListOfRawEvents(events)).finish()
381
            }
382
            Self::After(events) => {
383
                f.debug_tuple("After").field(&DebugListOfRawEvents(events)).finish()
384
            }
385
        }
386
    }
387
}
388
389
struct DebugInvitedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, InvitedRoomUpdate>);
390
391
#[cfg(not(tarpaulin_include))]
392
impl fmt::Debug for DebugInvitedRoomUpdates<'_> {
393
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394
        f.debug_map().entries(self.0.iter().map(|(k, v)| (k, DebugInvitedRoom(v)))).finish()
395
    }
396
}
397
398
struct DebugKnockedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, KnockedRoomUpdate>);
399
400
#[cfg(not(tarpaulin_include))]
401
impl fmt::Debug for DebugKnockedRoomUpdates<'_> {
402
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403
        f.debug_map().entries(self.0.iter().map(|(k, v)| (k, DebugKnockedRoom(v)))).finish()
404
    }
405
}
406
407
/// A notification triggered by a sync response.
408
#[derive(Clone)]
409
pub struct Notification {
410
    /// The actions to perform when the conditions for this rule are met.
411
    pub actions: Vec<Action>,
412
413
    /// The event that triggered the notification.
414
    pub event: RawAnySyncOrStrippedTimelineEvent,
415
}
416
417
#[cfg(not(tarpaulin_include))]
418
impl fmt::Debug for Notification {
419
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420
        let event_debug = match &self.event {
421
            RawAnySyncOrStrippedTimelineEvent::Sync(ev) => DebugRawEvent(ev),
422
            RawAnySyncOrStrippedTimelineEvent::Stripped(ev) => {
423
                DebugRawEvent(ev.cast_ref_unchecked())
424
            }
425
        };
426
427
        f.debug_struct("Notification")
428
            .field("actions", &self.actions)
429
            .field("event", &event_debug)
430
            .finish()
431
    }
432
}