Skip to main content

matrix_sdk_base/store/
avatar_cache.rs

1use std::collections::BTreeMap;
2
3use ruma::{
4    MxcUri, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, UserId,
5    events::room::member::SyncRoomMemberEvent,
6};
7use tracing::trace;
8
9use crate::{StateChanges, StateStore, StoreError, store::SaveLockedStateStore};
10
11/// A cache for keeping track of avatar changes in sync responses.
12#[derive(Debug)]
13pub struct AvatarCache {
14    store: SaveLockedStateStore,
15    changes: BTreeMap<OwnedRoomId, BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
16}
17
18impl AvatarCache {
19    /// Creates a new [`AvatarCache`].
20    pub fn new(store: SaveLockedStateStore) -> Self {
21        Self { store, changes: BTreeMap::new() }
22    }
23
24    /// Processes the room member event and checks if there was any change in
25    /// the avatar URL for the room member.
26    pub async fn handle_event(
27        &mut self,
28        state_changes: &StateChanges,
29        room_id: &RoomId,
30        member_event: &SyncRoomMemberEvent,
31    ) -> Result<(), StoreError> {
32        let user_id = member_event.sender();
33        if self.changes.get(room_id).is_some_and(|user_ids| user_ids.contains_key(user_id)) {
34            return Ok(());
35        }
36        match member_event {
37            SyncRoomMemberEvent::Original(original_event) => {
38                let avatar_url = original_event.content.avatar_url.clone();
39                self.add_to_changes_if_needed(state_changes, room_id, user_id, avatar_url).await;
40            }
41            SyncRoomMemberEvent::Redacted(_) => {
42                trace!("Redacted event, discarding avatar change for {:?}", user_id);
43            }
44        }
45        Ok(())
46    }
47
48    async fn add_to_changes_if_needed(
49        &mut self,
50        state_changes: &StateChanges,
51        room_id: &RoomId,
52        user_id: &UserId,
53        avatar: Option<OwnedMxcUri>,
54    ) {
55        if !self.is_same_avatar(state_changes, room_id, user_id, avatar.as_deref()).await {
56            trace!("Avatar for {} is different, saving to changes", user_id);
57            let change = self.changes.entry(room_id.to_owned()).or_default();
58            change.insert(user_id.to_owned(), avatar);
59        } else {
60            trace!("Avatar for {} is the same, not saving", user_id);
61        }
62    }
63
64    async fn is_same_avatar(
65        &self,
66        state_changes: &StateChanges,
67        room_id: &RoomId,
68        user_id: &UserId,
69        avatar: Option<&MxcUri>,
70    ) -> bool {
71        let current_avatar = if let Some(event) = state_changes.member(room_id, user_id) {
72            event.content.avatar_url
73        } else {
74            match self.store.get_profile(room_id, user_id).await {
75                Ok(Some(profile)) => profile.content.avatar_url,
76                Ok(None) => None,
77                Err(_) => None,
78            }
79        };
80
81        trace!(
82            "Current avatar for {}  in {} is: {:?}, new avatar is: {:?}",
83            user_id, room_id, current_avatar, avatar
84        );
85
86        match (current_avatar, avatar) {
87            (Some(current_avatar), Some(avatar)) => current_avatar == avatar,
88            (None, None) => true,
89            _ => false,
90        }
91    }
92
93    /// Removes and returns the avatar changes associated with the [`RoomId`],
94    /// if any.
95    pub fn remove_changes(
96        &mut self,
97        room_id: &RoomId,
98    ) -> Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>> {
99        self.changes.remove(room_id)
100    }
101}