fractal/session/view/content/room_details/permissions/
privileged_members.rs

1use std::collections::BTreeMap;
2
3use adw::subclass::prelude::*;
4use gtk::{gio, glib, glib::clone, prelude::*};
5use indexmap::IndexMap;
6use ruma::{Int, OwnedUserId};
7
8use super::MemberPowerLevel;
9use crate::{
10    session::model::{Permissions, PowerLevel, User},
11    utils::BoundObjectWeakRef,
12};
13
14mod imp {
15    use std::cell::{Cell, RefCell};
16
17    use super::*;
18
19    #[derive(Debug, Default, glib::Properties)]
20    #[properties(wrapper_type = super::PrivilegedMembers)]
21    pub struct PrivilegedMembers {
22        /// The list of members.
23        pub(super) list: RefCell<IndexMap<OwnedUserId, MemberPowerLevel>>,
24        /// The permissions to watch.
25        #[property(get, set = Self::set_permissions, construct_only)]
26        permissions: BoundObjectWeakRef<Permissions>,
27        /// Whether this list has changed.
28        #[property(get)]
29        changed: Cell<bool>,
30    }
31
32    #[glib::object_subclass]
33    impl ObjectSubclass for PrivilegedMembers {
34        const NAME: &'static str = "RoomDetailsPermissionsPrivilegedMembers";
35        type Type = super::PrivilegedMembers;
36        type Interfaces = (gio::ListModel,);
37    }
38
39    #[glib::derived_properties]
40    impl ObjectImpl for PrivilegedMembers {}
41
42    impl ListModelImpl for PrivilegedMembers {
43        fn item_type(&self) -> glib::Type {
44            MemberPowerLevel::static_type()
45        }
46
47        fn n_items(&self) -> u32 {
48            self.list.borrow().len() as u32
49        }
50
51        fn item(&self, position: u32) -> Option<glib::Object> {
52            self.list
53                .borrow()
54                .get_index(position as usize)
55                .map(|(_, member)| member.clone().upcast())
56        }
57    }
58
59    impl PrivilegedMembers {
60        /// Set the permissions to watch.
61        fn set_permissions(&self, permissions: &Permissions) {
62            let changed_handler = permissions.connect_changed(clone!(
63                #[weak(rename_to = imp)]
64                self,
65                move |_| {
66                    imp.update();
67                }
68            ));
69            self.permissions.set(permissions, vec![changed_handler]);
70
71            self.update();
72        }
73
74        /// Update this list.
75        fn update(&self) {
76            let Some(permissions) = self.permissions.obj() else {
77                return;
78            };
79            let Some(room) = permissions.room() else {
80                return;
81            };
82            let Some(session) = room.session() else {
83                return;
84            };
85
86            let members = room.get_or_create_members();
87            let mut users = permissions.power_levels().users;
88
89            let mut removed_users = Vec::new();
90            {
91                for user_id in self.list.borrow().keys() {
92                    if !users.contains_key(user_id) {
93                        removed_users.push(user_id.clone());
94                        continue;
95                    }
96
97                    // Remove known members so only new members are left.
98                    users.remove(user_id);
99                }
100            }
101
102            for user_id in removed_users {
103                self.remove_member(&user_id);
104            }
105
106            // Only new members are remaining.
107            let mut new_handlers = Vec::with_capacity(users.len());
108            let new_members = users.into_keys().map(|user_id| {
109                let user = members
110                    .get(&user_id)
111                    .and_upcast::<User>()
112                    .unwrap_or_else(|| {
113                        // Fallback to the remote cache if the user is not in the room anymore.
114                        session.remote_cache().user(user_id.clone()).upcast()
115                    });
116                let member = MemberPowerLevel::new(&user, &permissions);
117
118                let handler = member.connect_power_level_notify(clone!(
119                    #[weak(rename_to = imp)]
120                    self,
121                    move |_| {
122                        imp.update_changed();
123                    }
124                ));
125                new_handlers.push(handler);
126
127                (user_id, member)
128            });
129
130            self.add_members(new_members);
131        }
132
133        /// Remove the member with the given user ID from the list.
134        fn remove_member(&self, user_id: &OwnedUserId) {
135            let Some((pos, ..)) = self.list.borrow_mut().shift_remove_full(user_id) else {
136                return;
137            };
138
139            self.obj().items_changed(pos as u32, 1, 0);
140        }
141
142        /// Add the given members to the list.
143        pub(super) fn add_members(
144            &self,
145            members: impl ExactSizeIterator<Item = (OwnedUserId, MemberPowerLevel)>,
146        ) {
147            let pos = self.n_items();
148            let added = members.len() as u32;
149
150            self.list.borrow_mut().extend(members);
151
152            self.update_changed();
153            self.obj().items_changed(pos, 0, added);
154        }
155
156        /// Update whether the list has changed.
157        fn update_changed(&self) {
158            let changed = self.compute_changed();
159
160            if self.changed.get() == changed {
161                return;
162            }
163
164            self.changed.set(changed);
165            self.obj().notify_changed();
166        }
167
168        /// Compute whether the list has changed.
169        fn compute_changed(&self) -> bool {
170            let Some(permissions) = self.permissions.obj() else {
171                return false;
172            };
173
174            let users = permissions.power_levels().users;
175            let list = self.list.borrow();
176
177            if users.len() != list.len() {
178                return true;
179            }
180
181            for (user_id, member) in list.iter() {
182                let Some(pl) = users.get(user_id) else {
183                    // This is a new member.
184                    return true;
185                };
186
187                if member.power_level() != PowerLevel::from(*pl) {
188                    return true;
189                }
190            }
191
192            false
193        }
194    }
195}
196
197glib::wrapper! {
198    /// The list of members with custom power levels in a room.
199    pub struct PrivilegedMembers(ObjectSubclass<imp::PrivilegedMembers>)
200        @implements gio::ListModel;
201}
202
203impl PrivilegedMembers {
204    /// Constructs a new `PrivilegedMembers` with the given permissions.
205    pub fn new(permissions: &Permissions) -> Self {
206        glib::Object::builder()
207            .property("permissions", permissions)
208            .build()
209    }
210
211    /// Add the given members to the list.
212    pub(crate) fn add_members(
213        &self,
214        members: impl ExactSizeIterator<Item = (OwnedUserId, MemberPowerLevel)>,
215    ) {
216        let imp = self.imp();
217
218        let mut new_members = Vec::with_capacity(members.len());
219
220        {
221            let list = imp.list.borrow();
222            for (user_id, new_member) in members {
223                if let Some(member) = list.get(&user_id) {
224                    member.set_power_level(new_member.power_level());
225                } else {
226                    new_members.push((user_id, new_member));
227                }
228            }
229        }
230
231        if !new_members.is_empty() {
232            self.imp().add_members(new_members.into_iter());
233        }
234    }
235
236    /// Collect the list of members.
237    pub(crate) fn collect(&self) -> BTreeMap<OwnedUserId, Int> {
238        self.imp()
239            .list
240            .borrow()
241            .values()
242            .filter_map(MemberPowerLevel::to_parts)
243            .collect()
244    }
245}