matrix_ui_serializable/user/
user_profile.rs1use std::{
2 collections::{BTreeMap, BTreeSet, btree_map::Entry},
3 sync::{Arc, LazyLock},
4};
5
6use crossbeam_queue::SegQueue;
7use matrix_sdk::{
8 room::RoomMember,
9 ruma::{OwnedMxcUri, OwnedRoomId, OwnedUserId},
10};
11use serde::Serialize;
12use tokio::sync::RwLock;
13
14use crate::models::{
15 async_requests::{MatrixRequest, submit_async_request},
16 state_updater::StateUpdater,
17};
18
19use crate::init::singletons::{UIUpdateMessage, broadcast_event};
20
21#[derive(Debug, Clone, Serialize)]
23#[serde(rename_all = "camelCase")]
24pub struct UserProfile {
25 pub user_id: OwnedUserId,
26 pub username: Option<String>,
30 pub avatar_url: Option<OwnedMxcUri>,
31}
32impl UserProfile {
33 pub fn _displayable_name(&self) -> &str {
35 if let Some(un) = self.username.as_ref() {
36 if !un.is_empty() {
37 return un.as_str();
38 }
39 }
40 self.user_id.as_str()
41 }
42}
43
44static PENDING_USER_PROFILE_UPDATES: SegQueue<UserProfileUpdate> = SegQueue::new();
46
47pub fn enqueue_user_profile_update(update: UserProfileUpdate) {
49 PENDING_USER_PROFILE_UPDATES.push(update);
50 broadcast_event(UIUpdateMessage::RefreshUI).expect("Couldn't broadcast event to UI");
51}
52
53pub enum UserProfileUpdate {
56 Full {
58 new_profile: UserProfile,
59 room_id: OwnedRoomId,
60 _room_member: RoomMember,
61 },
62 RoomMemberOnly {
64 room_id: OwnedRoomId,
65 room_member: RoomMember,
66 },
67 UserProfileOnly(UserProfile),
69}
70
71impl UserProfileUpdate {
72 fn apply_to_cache(self, cache: &mut BTreeMap<OwnedUserId, UserProfileCacheEntry>) {
74 match self {
75 UserProfileUpdate::Full {
76 new_profile,
77 room_id,
78 _room_member: _,
79 } => match cache.entry(new_profile.user_id.clone()) {
80 Entry::Occupied(mut entry) => match entry.get_mut() {
81 e @ UserProfileCacheEntry::Requested => {
82 *e = UserProfileCacheEntry::Loaded {
83 user_profile: new_profile,
84 rooms: {
85 let mut rooms = BTreeSet::new();
86 rooms.insert(room_id);
87 rooms
88 },
89 };
90 }
91 UserProfileCacheEntry::Loaded {
92 user_profile,
93 rooms,
94 } => {
95 *user_profile = new_profile;
96 rooms.insert(room_id);
97 }
98 },
99 Entry::Vacant(entry) => {
100 entry.insert(UserProfileCacheEntry::Loaded {
101 user_profile: new_profile,
102 rooms: {
103 let mut rooms = BTreeSet::new();
104 rooms.insert(room_id);
105 rooms
106 },
107 });
108 }
109 },
110 UserProfileUpdate::RoomMemberOnly {
111 room_id,
112 room_member,
113 } => {
114 match cache.entry(room_member.user_id().to_owned()) {
115 Entry::Occupied(mut entry) => match entry.get_mut() {
116 e @ UserProfileCacheEntry::Requested => {
117 eprintln!(
119 "BUG: User profile cache entry was `Requested` for user {} when handling RoomMemberOnly update",
120 room_member.user_id()
121 );
122 *e = UserProfileCacheEntry::Loaded {
123 user_profile: UserProfile {
124 user_id: room_member.user_id().to_owned(),
125 username: None,
126 avatar_url: room_member.avatar_url().map(|url| url.to_owned()),
127 },
128 rooms: {
129 let mut rooms = BTreeSet::new();
130 rooms.insert(room_id);
131 rooms
132 },
133 };
134 }
135 UserProfileCacheEntry::Loaded { rooms, .. } => {
136 rooms.insert(room_id);
137 }
138 },
139 Entry::Vacant(entry) => {
140 eprintln!(
142 "BUG: User profile cache entry not found for user {} when handling RoomMemberOnly update",
143 room_member.user_id()
144 );
145 entry.insert(UserProfileCacheEntry::Loaded {
146 user_profile: UserProfile {
147 user_id: room_member.user_id().to_owned(),
148 username: None,
149 avatar_url: room_member.avatar_url().map(|url| url.to_owned()),
150 },
151 rooms: {
152 let mut rooms = BTreeSet::new();
153 rooms.insert(room_id);
154 rooms
155 },
156 });
157 }
158 }
159 }
160 UserProfileUpdate::UserProfileOnly(new_profile) => {
161 match cache.entry(new_profile.user_id.clone()) {
162 Entry::Occupied(mut entry) => match entry.get_mut() {
163 e @ UserProfileCacheEntry::Requested => {
164 *e = UserProfileCacheEntry::Loaded {
165 user_profile: new_profile,
166 rooms: BTreeSet::new(),
167 };
168 }
169 UserProfileCacheEntry::Loaded { user_profile, .. } => {
170 *user_profile = new_profile;
171 }
172 },
173 Entry::Vacant(entry) => {
174 entry.insert(UserProfileCacheEntry::Loaded {
175 user_profile: new_profile,
176 rooms: BTreeSet::new(),
177 });
178 }
179 }
180 }
181 }
182 }
183}
184
185#[derive(Debug, Clone, Serialize)]
187pub struct UserProfileMap(BTreeMap<OwnedUserId, UserProfileCacheEntry>);
188
189static USER_PROFILE_CACHE: LazyLock<RwLock<UserProfileMap>> =
191 LazyLock::new(|| RwLock::new(UserProfileMap(BTreeMap::new())));
192
193pub async fn process_user_profile_updates(updaters: &Arc<Box<dyn StateUpdater>>) -> bool {
195 let mut updated = false;
196 if PENDING_USER_PROFILE_UPDATES.is_empty() {
197 return updated; };
199 {
200 let mut lock = USER_PROFILE_CACHE.write().await;
201 while let Some(update) = PENDING_USER_PROFILE_UPDATES.pop() {
202 update.apply_to_cache(&mut lock.0);
204 updated = true;
205 }
206 } if updated {
208 let lock = USER_PROFILE_CACHE.read().await;
209 updaters
210 .update_profile(&lock)
211 .expect("Couldn't update profiles frontend state");
212 }
213 updated
214}
215
216pub async fn fetch_user_profile(user_id: OwnedUserId, room_id: Option<OwnedRoomId>) -> bool {
218 let mut lock = USER_PROFILE_CACHE.write().await;
219 match lock.0.entry(user_id) {
220 Entry::Occupied(_) => true,
221 Entry::Vacant(entry) => {
222 submit_async_request(MatrixRequest::GetUserProfile {
223 user_id: entry.key().clone(),
224 room_id,
225 local_only: false,
226 });
227 entry.insert(UserProfileCacheEntry::Requested);
228 false
229 }
230 }
231}
232
233#[derive(Debug, Clone, Serialize)]
234#[serde(
235 rename_all = "camelCase",
236 rename_all_fields = "camelCase",
237 tag = "state",
238 content = "data"
239)]
240enum UserProfileCacheEntry {
241 Requested,
243 Loaded {
245 #[serde(flatten)]
246 user_profile: UserProfile,
247 rooms: BTreeSet<OwnedRoomId>,
248 },
249}