Skip to main content

matrix_sdk/
sync.rs

1// Copyright 2023 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::{
18    collections::{BTreeMap, btree_map},
19    fmt,
20    time::Duration,
21};
22
23pub use matrix_sdk_base::sync::*;
24use matrix_sdk_base::{
25    debug::{
26        DebugInvitedRoom, DebugKnockedRoom, DebugListOfProcessedToDeviceEvents,
27        DebugListOfRawEventsNoId,
28    },
29    sleep::sleep,
30    sync::SyncResponse as BaseSyncResponse,
31    timer,
32};
33use matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent;
34use ruma::{
35    OwnedRoomId, RoomId,
36    api::client::sync::sync_events::{
37        self,
38        v3::{InvitedRoom, KnockedRoom},
39    },
40    events::{AnyGlobalAccountDataEvent, presence::PresenceEvent},
41    serde::Raw,
42    time::Instant,
43};
44use tracing::{Level, error, instrument, warn};
45
46use crate::{Client, Result, Room, event_handler::HandlerKind};
47
48/// The processed response of a `/sync` request.
49#[derive(Clone, Default)]
50pub struct SyncResponse {
51    /// The batch token to supply in the `since` param of the next `/sync`
52    /// request.
53    pub next_batch: String,
54    /// Updates to rooms.
55    pub rooms: RoomUpdates,
56    /// Updates to the presence status of other users.
57    pub presence: Vec<Raw<PresenceEvent>>,
58    /// The global private data created by this user.
59    pub account_data: Vec<Raw<AnyGlobalAccountDataEvent>>,
60    /// Messages sent directly between devices.
61    pub to_device: Vec<ProcessedToDeviceEvent>,
62    /// New notifications per room.
63    pub notifications: BTreeMap<OwnedRoomId, Vec<Notification>>,
64}
65
66impl SyncResponse {
67    pub(crate) fn new(next_batch: String, base_response: BaseSyncResponse) -> Self {
68        let BaseSyncResponse { rooms, presence, account_data, to_device, notifications } =
69            base_response;
70
71        Self { next_batch, rooms, presence, account_data, to_device, notifications }
72    }
73}
74
75#[cfg(not(tarpaulin_include))]
76impl fmt::Debug for SyncResponse {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        f.debug_struct("SyncResponse")
79            .field("next_batch", &self.next_batch)
80            .field("rooms", &self.rooms)
81            .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
82            .field("to_device", &DebugListOfProcessedToDeviceEvents(&self.to_device))
83            .field("notifications", &self.notifications)
84            .finish_non_exhaustive()
85    }
86}
87
88/// A batch of updates to a room.
89#[derive(Clone)]
90pub enum RoomUpdate {
91    /// Updates to a room the user is no longer in.
92    Left {
93        /// Room object with general information on the room.
94        room: Room,
95        /// Updates to the room.
96        updates: LeftRoomUpdate,
97    },
98    /// Updates to a room the user is currently in.
99    Joined {
100        /// Room object with general information on the room.
101        room: Room,
102        /// Updates to the room.
103        updates: JoinedRoomUpdate,
104    },
105    /// Updates to a room the user is invited to.
106    Invited {
107        /// Room object with general information on the room.
108        room: Room,
109        /// Updates to the room.
110        updates: InvitedRoom,
111    },
112    /// Updates to a room the user knocked on.
113    Knocked {
114        /// Room object with general information on the room.
115        room: Room,
116        /// Updates to the room.
117        updates: KnockedRoom,
118    },
119}
120
121#[cfg(not(tarpaulin_include))]
122impl fmt::Debug for RoomUpdate {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        match self {
125            Self::Left { room, updates } => {
126                f.debug_struct("Left").field("room", room).field("updates", updates).finish()
127            }
128            Self::Joined { room, updates } => {
129                f.debug_struct("Joined").field("room", room).field("updates", updates).finish()
130            }
131            Self::Invited { room, updates } => f
132                .debug_struct("Invited")
133                .field("room", room)
134                .field("updates", &DebugInvitedRoom(updates))
135                .finish(),
136            Self::Knocked { room, updates } => f
137                .debug_struct("Knocked")
138                .field("room", room)
139                .field("updates", &DebugKnockedRoom(updates))
140                .finish(),
141        }
142    }
143}
144
145/// Internal functionality related to getting events from the server
146/// (`sync_events` endpoint)
147impl Client {
148    /// Receive a sync response, compute extra information out of it and store
149    /// the interesting bits in the database, then call all the handlers.
150    pub(crate) async fn process_sync(
151        &self,
152        response: sync_events::v3::Response,
153    ) -> Result<BaseSyncResponse> {
154        subscribe_to_room_latest_events(
155            self,
156            response.rooms.join.keys().chain(response.rooms.leave.keys()),
157        )
158        .await;
159
160        let response = Box::pin(self.base_client().receive_sync_response(response)).await?;
161
162        // Some new keys might have been received, so trigger a backup if
163        // needed.
164        #[cfg(feature = "e2e-encryption")]
165        self.encryption().backups().maybe_trigger_backup();
166
167        // The Event Cache now receives the room updates.
168        if let Some(event_cache) = self.event_cache() {
169            Box::pin(event_cache.handle_room_updates(response.rooms.clone())).await?;
170        }
171
172        // Finally, let's call the sync response handler.
173        self.call_sync_response_handlers(&response).await?;
174
175        Ok(response)
176    }
177
178    /// Calls event handlers and notification handlers after a sync response has
179    /// been processed.
180    ///
181    /// At this point, the sync response's data has been taken into account and
182    /// persisted in the store, if needs be. This function is only calling the
183    /// event, room update and notification handlers.
184    #[tracing::instrument(skip(self, response))]
185    pub(crate) async fn call_sync_response_handlers(
186        &self,
187        response: &BaseSyncResponse,
188    ) -> Result<()> {
189        let _timer = timer!(Level::TRACE, "_method");
190
191        let BaseSyncResponse { rooms, presence, account_data, to_device, notifications } = response;
192
193        let timer = timer!(Level::DEBUG, "Run event handlers");
194
195        self.handle_sync_events(HandlerKind::GlobalAccountData, None, account_data).await?;
196        self.handle_sync_events(HandlerKind::Presence, None, presence).await?;
197        self.handle_sync_to_device_events(to_device).await?;
198
199        // Broadcast updates if and only if there is at least one receiver. The
200        // goal is to avoid to clone `rooms`.
201        //
202        // What if a receiver is created right after `receiver_count` is called?
203        // Well, the same thing as if this condition didn't exist: if the
204        // receiver would be created right after `send` is called, it would have
205        // missed the update. That's okay.
206        if self.inner.room_updates_sender.receiver_count() > 0 {
207            // Ignore errors when there are no receivers.
208            let _ = self.inner.room_updates_sender.send(rooms.clone());
209        }
210
211        for (room_id, updates) in &rooms.joined {
212            let Some(room) = self.get_room(room_id) else {
213                error!(?room_id, "Can't call event handler, room not found");
214                continue;
215            };
216
217            self.send_room_update(room_id, || RoomUpdate::Joined {
218                room: room.clone(),
219                updates: updates.clone(),
220            });
221
222            let JoinedRoomUpdate {
223                unread_notifications: _,
224                timeline,
225                state,
226                account_data,
227                ephemeral,
228                ambiguity_changes: _,
229                avatar_changes: _,
230            } = updates;
231
232            let room = Some(&room);
233            self.handle_sync_events(HandlerKind::RoomAccountData, room, account_data).await?;
234            self.handle_sync_state_events(room, state).await?;
235            self.handle_sync_timeline_events(room, &timeline.events).await?;
236            // Handle ephemeral events after timeline, read receipts in here
237            // could refer to timeline events from the same response.
238            self.handle_sync_events(HandlerKind::EphemeralRoomData, room, ephemeral).await?;
239        }
240
241        for (room_id, updates) in &rooms.left {
242            let Some(room) = self.get_room(room_id) else {
243                error!(?room_id, "Can't call event handler, room not found");
244                continue;
245            };
246
247            self.send_room_update(room_id, || RoomUpdate::Left {
248                room: room.clone(),
249                updates: updates.clone(),
250            });
251
252            let LeftRoomUpdate { timeline, state, account_data, ambiguity_changes: _ } = updates;
253
254            let room = Some(&room);
255            self.handle_sync_events(HandlerKind::RoomAccountData, room, account_data).await?;
256            self.handle_sync_state_events(room, state).await?;
257            self.handle_sync_timeline_events(room, &timeline.events).await?;
258        }
259
260        for (room_id, updates) in &rooms.invited {
261            let Some(room) = self.get_room(room_id) else {
262                error!(?room_id, "Can't call event handler, room not found");
263                continue;
264            };
265
266            self.send_room_update(room_id, || RoomUpdate::Invited {
267                room: room.clone(),
268                updates: updates.clone(),
269            });
270
271            let invite_state = &updates.invite_state.events;
272            self.handle_sync_events(HandlerKind::StrippedState, Some(&room), invite_state).await?;
273        }
274
275        for (room_id, updates) in &rooms.knocked {
276            let Some(room) = self.get_room(room_id) else {
277                error!(?room_id, "Can't call event handler, room not found");
278                continue;
279            };
280
281            self.send_room_update(room_id, || RoomUpdate::Knocked {
282                room: room.clone(),
283                updates: updates.clone(),
284            });
285
286            let knock_state = &updates.knock_state.events;
287            self.handle_sync_events(HandlerKind::StrippedState, Some(&room), knock_state).await?;
288        }
289
290        drop(timer);
291
292        let timer = timer!(Level::DEBUG, "Run notification handlers");
293
294        // Construct notification event handler futures
295        let mut futures = Vec::new();
296        for handler in &*self.notification_handlers().await {
297            for (room_id, room_notifications) in notifications {
298                let Some(room) = self.get_room(room_id) else {
299                    warn!(?room_id, "Can't call notification handler, room not found");
300                    continue;
301                };
302
303                futures.extend(room_notifications.iter().map(|notification| {
304                    (handler)(notification.clone(), room.clone(), self.clone())
305                }));
306            }
307        }
308
309        // Run the notification handler futures with the
310        // `self.notification_handlers` lock no longer being held, in order.
311        for fut in futures {
312            fut.await;
313        }
314
315        drop(timer);
316
317        Ok(())
318    }
319
320    fn send_room_update(&self, room_id: &RoomId, make_msg: impl FnOnce() -> RoomUpdate) {
321        if let btree_map::Entry::Occupied(entry) =
322            self.inner.room_update_channels.lock().unwrap().entry(room_id.to_owned())
323        {
324            let tx = entry.get();
325            if tx.receiver_count() == 0 {
326                entry.remove();
327            } else {
328                _ = tx.send(make_msg());
329            }
330        }
331    }
332
333    async fn sleep() {
334        sleep(Duration::from_secs(1)).await;
335    }
336
337    pub(crate) async fn sync_loop_helper(
338        &self,
339        sync_settings: &mut crate::config::SyncSettings,
340    ) -> Result<SyncResponse> {
341        let response = self.sync_once(sync_settings.clone()).await;
342
343        match response {
344            Ok(r) => {
345                sync_settings.token = r.next_batch.clone().into();
346                Ok(r)
347            }
348            Err(e) => {
349                error!("Received an invalid response: {e}");
350                Err(e)
351            }
352        }
353    }
354
355    pub(crate) async fn delay_sync(last_sync_time: &mut Option<Instant>) {
356        let now = Instant::now();
357
358        // If the last sync happened less than a second ago, sleep for a while
359        // to not hammer out requests if the server doesn't respect the sync
360        // timeout.
361        if let Some(t) = last_sync_time
362            && now - *t <= Duration::from_secs(1)
363        {
364            Self::sleep().await;
365        }
366
367        *last_sync_time = Some(now);
368    }
369}
370
371/// Call `LatestEvents::listen_to_room` for rooms in `response` if
372/// `LatestEvents` is enabled.
373///
374/// That way, the latest event is computed and updated for all rooms receiving
375/// an update from the sync.
376#[instrument(skip_all)]
377pub(crate) async fn subscribe_to_room_latest_events<'a, R>(client: &'a Client, room_ids: R)
378where
379    R: Iterator<Item = &'a OwnedRoomId>,
380{
381    if let Some(latest_events) = client.latest_events().await {
382        for room_id in room_ids {
383            if let Err(error) = latest_events.listen_to_room(room_id).await {
384                error!(?error, ?room_id, "Failed to listen to the latest event for this room");
385            }
386        }
387    }
388}