matrix_sdk_base/response_processors/account_data/
global.rs

1// Copyright 2024 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
15use std::{
16    collections::{BTreeMap, HashMap, HashSet},
17    mem,
18};
19
20use ruma::{
21    events::{
22        direct::OwnedDirectUserIdentifier, AnyGlobalAccountDataEvent, GlobalAccountDataEventType,
23    },
24    serde::Raw,
25    RoomId,
26};
27use tracing::{debug, instrument, trace, warn};
28
29use super::super::Context;
30use crate::{store::BaseStateStore, RoomInfo, StateChanges};
31
32/// Create the [`Global`] account data processor.
33pub fn global(events: &[Raw<AnyGlobalAccountDataEvent>]) -> Global {
34    Global::process(events)
35}
36
37#[must_use]
38pub struct Global {
39    parsed_events: Vec<AnyGlobalAccountDataEvent>,
40    raw_by_type: BTreeMap<GlobalAccountDataEventType, Raw<AnyGlobalAccountDataEvent>>,
41}
42
43impl Global {
44    /// Creates a new processor for global account data.
45    fn process(events: &[Raw<AnyGlobalAccountDataEvent>]) -> Self {
46        let mut raw_by_type = BTreeMap::new();
47        let mut parsed_events = Vec::new();
48
49        for raw_event in events {
50            let event = match raw_event.deserialize() {
51                Ok(e) => e,
52                Err(e) => {
53                    let event_type: Option<String> = raw_event.get_field("type").ok().flatten();
54                    warn!(event_type, "Failed to deserialize a global account data event: {e}");
55                    continue;
56                }
57            };
58
59            raw_by_type.insert(event.event_type(), raw_event.clone());
60            parsed_events.push(event);
61        }
62
63        Self { raw_by_type, parsed_events }
64    }
65
66    /// Returns the push rules found by this processor.
67    pub fn push_rules(&self) -> Option<&Raw<AnyGlobalAccountDataEvent>> {
68        self.raw_by_type.get(&GlobalAccountDataEventType::PushRules)
69    }
70
71    /// Processes the direct rooms in a sync response:
72    ///
73    /// Given a [`StateChanges`] instance, processes any direct room info
74    /// from the global account data and adds it to the room infos to
75    /// save.
76    #[instrument(skip_all)]
77    fn process_direct_rooms(
78        &self,
79        events: &[AnyGlobalAccountDataEvent],
80        state_store: &BaseStateStore,
81        state_changes: &mut StateChanges,
82    ) {
83        for event in events {
84            let AnyGlobalAccountDataEvent::Direct(direct_event) = event else { continue };
85
86            let mut new_dms = HashMap::<&RoomId, HashSet<OwnedDirectUserIdentifier>>::new();
87
88            for (user_identifier, rooms) in direct_event.content.iter() {
89                for room_id in rooms {
90                    new_dms.entry(room_id).or_default().insert(user_identifier.clone());
91                }
92            }
93
94            let rooms = state_store.rooms();
95            let mut old_dms = rooms
96                .iter()
97                .filter_map(|r| {
98                    let direct_targets = r.direct_targets();
99                    (!direct_targets.is_empty()).then(|| (r.room_id(), direct_targets))
100                })
101                .collect::<HashMap<_, _>>();
102
103            // Update the direct targets of rooms if they changed.
104            for (room_id, new_direct_targets) in new_dms {
105                if let Some(old_direct_targets) = old_dms.remove(&room_id) {
106                    if old_direct_targets == new_direct_targets {
107                        continue;
108                    }
109                }
110                trace!(?room_id, targets = ?new_direct_targets, "Marking room as direct room");
111                map_info(room_id, state_changes, state_store, |info| {
112                    info.base_info.dm_targets = new_direct_targets;
113                });
114            }
115
116            // Remove the targets of old direct chats.
117            for room_id in old_dms.keys() {
118                trace!(?room_id, "Unmarking room as direct room");
119                map_info(room_id, state_changes, state_store, |info| {
120                    info.base_info.dm_targets.clear();
121                });
122            }
123        }
124    }
125
126    /// Applies the processed data to the state changes and the state store.
127    pub async fn apply(mut self, context: &mut Context, state_store: &BaseStateStore) {
128        // Fill in the content of `changes.account_data`.
129        mem::swap(&mut context.state_changes.account_data, &mut self.raw_by_type);
130
131        // Process direct rooms.
132        let has_new_direct_room_data = self
133            .parsed_events
134            .iter()
135            .any(|event| event.event_type() == GlobalAccountDataEventType::Direct);
136
137        if has_new_direct_room_data {
138            self.process_direct_rooms(&self.parsed_events, state_store, &mut context.state_changes);
139        } else if let Ok(Some(direct_account_data)) =
140            state_store.get_account_data_event(GlobalAccountDataEventType::Direct).await
141        {
142            debug!("Found direct room data in the Store, applying it");
143            if let Ok(direct_account_data) = direct_account_data.deserialize() {
144                self.process_direct_rooms(
145                    &[direct_account_data],
146                    state_store,
147                    &mut context.state_changes,
148                );
149            } else {
150                warn!("Failed to deserialize direct room account data");
151            }
152        }
153    }
154}
155
156/// Applies a function to an existing `RoomInfo` if present in changes, or one
157/// loaded from the database.
158fn map_info<F: FnOnce(&mut RoomInfo)>(
159    room_id: &RoomId,
160    changes: &mut StateChanges,
161    store: &BaseStateStore,
162    f: F,
163) {
164    if let Some(info) = changes.room_infos.get_mut(room_id) {
165        f(info);
166    } else if let Some(room) = store.room(room_id) {
167        let mut info = room.clone_info();
168        f(&mut info);
169        changes.add_room(info);
170    } else {
171        debug!(room = %room_id, "couldn't find room in state changes or store");
172    }
173}