matrix_sdk_base/response_processors/
changes.rs

1// Copyright 2025 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 eyeball::SharedObservable;
16use matrix_sdk_common::timer;
17use ruma::{
18    events::{GlobalAccountDataEventType, ignored_user_list::IgnoredUserListEvent},
19    serde::Raw,
20};
21use tracing::{error, instrument, trace};
22
23use super::Context;
24use crate::{
25    Result,
26    store::{BaseStateStore, StateStoreExt as _},
27};
28
29/// Save the [`StateChanges`] from the [`Context`] inside the [`BaseStateStore`]
30/// only! The changes aren't applied on the in-memory rooms.
31#[instrument(skip_all)]
32pub async fn save_only(context: Context, state_store: &BaseStateStore) -> Result<()> {
33    let _timer = timer!(tracing::Level::TRACE, "_method");
34
35    save_changes(&context, state_store, None).await?;
36    broadcast_room_info_notable_updates(&context, state_store);
37
38    Ok(())
39}
40
41/// Save the [`StateChanges`] from the [`Context`] inside the
42/// [`BaseStateStore`], and apply them on the in-memory rooms.
43#[instrument(skip_all)]
44pub async fn save_and_apply(
45    context: Context,
46    state_store: &BaseStateStore,
47    ignore_user_list_changes: &SharedObservable<Vec<String>>,
48    sync_token: Option<String>,
49) -> Result<()> {
50    let _timer = timer!(tracing::Level::TRACE, "_method");
51
52    trace!("ready to submit changes to store");
53
54    let previous_ignored_user_list =
55        state_store.get_account_data_event_static().await.ok().flatten();
56
57    save_changes(&context, state_store, sync_token).await?;
58    apply_changes(&context, ignore_user_list_changes, previous_ignored_user_list);
59    broadcast_room_info_notable_updates(&context, state_store);
60
61    trace!("applied changes");
62
63    Ok(())
64}
65
66async fn save_changes(
67    context: &Context,
68    state_store: &BaseStateStore,
69    sync_token: Option<String>,
70) -> Result<()> {
71    state_store.save_changes(&context.state_changes).await?;
72
73    if let Some(sync_token) = sync_token {
74        *state_store.sync_token.write().await = Some(sync_token);
75    }
76
77    Ok(())
78}
79
80fn apply_changes(
81    context: &Context,
82    ignore_user_list_changes: &SharedObservable<Vec<String>>,
83    previous_ignored_user_list: Option<Raw<IgnoredUserListEvent>>,
84) {
85    if let Some(event) =
86        context.state_changes.account_data.get(&GlobalAccountDataEventType::IgnoredUserList)
87    {
88        match event.deserialize_as_unchecked::<IgnoredUserListEvent>() {
89            Ok(event) => {
90                let user_ids: Vec<String> =
91                    event.content.ignored_users.keys().map(|id| id.to_string()).collect();
92
93                // Try to only trigger the observable if the ignored user list has changed,
94                // from the previous time we've seen it. If we couldn't load the previous event
95                // for any reason, always trigger.
96                if let Some(prev_user_ids) =
97                    previous_ignored_user_list.and_then(|raw| raw.deserialize().ok()).map(|event| {
98                        event
99                            .content
100                            .ignored_users
101                            .keys()
102                            .map(|id| id.to_string())
103                            .collect::<Vec<_>>()
104                    })
105                {
106                    if user_ids != prev_user_ids {
107                        ignore_user_list_changes.set(user_ids);
108                    }
109                } else {
110                    ignore_user_list_changes.set(user_ids);
111                }
112            }
113
114            Err(error) => {
115                error!("Failed to deserialize ignored user list event: {error}")
116            }
117        }
118    }
119}
120
121fn broadcast_room_info_notable_updates(context: &Context, state_store: &BaseStateStore) {
122    for (room_id, room_info) in &context.state_changes.room_infos {
123        if let Some(room) = state_store.room(room_id) {
124            let room_info_notable_update_reasons =
125                context.room_info_notable_updates.get(room_id).copied().unwrap_or_default();
126
127            room.set_room_info(room_info.clone(), room_info_notable_update_reasons)
128        }
129    }
130}