[fractal/fractal-next] room: Introduce MemberList



commit 02648c7424d1f765bae013a92aa371cc037327da
Author: Enterprisey <59171-enterprisey users noreply gitlab gnome org>
Date:   Sun Nov 7 00:54:39 2021 -0700

    room: Introduce MemberList

 src/session/room/event.rs       |   1 +
 src/session/room/member_list.rs | 159 ++++++++++++++++++++++++++++++++++++++++
 src/session/room/mod.rs         |  80 ++++++++------------
 3 files changed, 189 insertions(+), 51 deletions(-)
---
diff --git a/src/session/room/event.rs b/src/session/room/event.rs
index cfe60c79..5cf4d87c 100644
--- a/src/session/room/event.rs
+++ b/src/session/room/event.rs
@@ -167,6 +167,7 @@ impl Event {
             .room
             .get()
             .unwrap()
+            .members()
             .member_by_id(&self.matrix_sender())
     }
 
diff --git a/src/session/room/member_list.rs b/src/session/room/member_list.rs
new file mode 100644
index 00000000..7ca90a96
--- /dev/null
+++ b/src/session/room/member_list.rs
@@ -0,0 +1,159 @@
+use gtk::{gio, glib, prelude::*, subclass::prelude::*};
+use indexmap::IndexMap;
+
+use crate::session::room::{Member, Room, UserId};
+
+use matrix_sdk::ruma::events::{room::member::RoomMemberEventContent, SyncStateEvent};
+
+mod imp {
+    use super::*;
+    use glib::object::WeakRef;
+    use once_cell::{sync::Lazy, unsync::OnceCell};
+    use std::cell::RefCell;
+
+    #[derive(Debug, Default)]
+    pub struct MemberList {
+        pub members: RefCell<IndexMap<UserId, Member>>,
+        pub room: OnceCell<WeakRef<Room>>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for MemberList {
+        const NAME: &'static str = "MemberList";
+        type Type = super::MemberList;
+        type ParentType = glib::Object;
+        type Interfaces = (gio::ListModel,);
+    }
+
+    impl ObjectImpl for MemberList {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![glib::ParamSpec::new_object(
+                    "room",
+                    "Room",
+                    "The associated room",
+                    Room::static_type(),
+                    glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                )]
+            });
+
+            PROPERTIES.as_ref()
+        }
+
+        fn set_property(
+            &self,
+            _obj: &Self::Type,
+            _id: usize,
+            value: &glib::Value,
+            pspec: &glib::ParamSpec,
+        ) {
+            match pspec.name() {
+                "room" => self
+                    .room
+                    .set(value.get::<Room>().unwrap().downgrade())
+                    .unwrap(),
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "room" => obj.room().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+    }
+
+    impl ListModelImpl for MemberList {
+        fn item_type(&self, _list_model: &Self::Type) -> glib::Type {
+            Member::static_type()
+        }
+        fn n_items(&self, _list_model: &Self::Type) -> u32 {
+            self.members.borrow().len() as u32
+        }
+        fn item(&self, _list_model: &Self::Type, position: u32) -> Option<glib::Object> {
+            let members = self.members.borrow();
+
+            members
+                .get_index(position as usize)
+                .map(|(_user_id, member)| member.clone().upcast::<glib::Object>())
+        }
+    }
+}
+
+glib::wrapper! {
+    /// List of all Members in a room. Implements ListModel.
+    ///
+    /// Members are sorted in "insertion order", not anything useful.
+    pub struct MemberList(ObjectSubclass<imp::MemberList>)
+        @implements gio::ListModel;
+}
+
+impl MemberList {
+    pub fn new(room: &Room) -> Self {
+        glib::Object::new(&[("room", room)]).expect("Failed to create MemberList")
+    }
+
+    pub fn room(&self) -> Room {
+        let priv_ = imp::MemberList::from_instance(self);
+        priv_.room.get().unwrap().upgrade().unwrap()
+    }
+
+    /// Updates members with the given RoomMember values.
+    ///
+    /// If some of the values do not correspond to existing members, new members
+    /// are created.
+    pub fn update_from_room_members(&self, new_members: &[matrix_sdk::RoomMember]) {
+        let mut members = imp::MemberList::from_instance(self).members.borrow_mut();
+        let prev_len = members.len();
+        for member in new_members {
+            members
+                .entry(member.user_id().clone())
+                .or_insert_with(|| Member::new(&self.room(), member.user_id()))
+                .update_from_room_member(&member);
+        }
+        let num_members_added = members.len().saturating_sub(prev_len);
+
+        // We can't have the borrow active when items_changed is emitted because that will probably
+        // cause reads of the members field.
+        std::mem::drop(members);
+        if num_members_added > 0 {
+            // IndexMap preserves insertion order, so all the new items will be at the end.
+            self.items_changed(prev_len as u32, 0, num_members_added as u32);
+        }
+    }
+
+    /// Returns the member with the given ID.
+    ///
+    /// Creates a new member first if there is no member with the given ID.
+    pub fn member_by_id(&self, user_id: &UserId) -> Member {
+        let mut members = imp::MemberList::from_instance(self).members.borrow_mut();
+        let mut was_member_added = false;
+        let prev_len = members.len();
+        let member = members
+            .entry(user_id.clone())
+            .or_insert_with(|| {
+                was_member_added = true;
+                Member::new(&self.room(), user_id)
+            })
+            .clone();
+
+        // We can't have the borrow active when items_changed is emitted because that will probably
+        // cause reads of the members field.
+        std::mem::drop(members);
+        if was_member_added {
+            // IndexMap preserves insertion order so the new member will be at the end.
+            self.items_changed(prev_len as u32, 0, 1);
+        }
+
+        member
+    }
+
+    /// Updates a room member based on the room member state event.
+    ///
+    /// Creates a new member first if there is no member matching the given event.
+    pub fn update_member_for_member_event(&self, event: &SyncStateEvent<RoomMemberEventContent>) {
+        self.member_by_id(&event.sender)
+            .update_from_member_event(event);
+    }
+}
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index 701e5bbd..a7061c5d 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -2,6 +2,7 @@ mod event;
 mod highlight_flags;
 mod item;
 mod member;
+mod member_list;
 mod power_levels;
 mod room_type;
 mod timeline;
@@ -25,7 +26,7 @@ use matrix_sdk::{
         api::client::r0::sync::sync_events::InvitedRoom,
         events::{
             room::{
-                member::{MembershipState, RoomMemberEventContent},
+                member::MembershipState,
                 message::{
                     EmoteMessageEventContent, MessageType, RoomMessageEventContent,
                     TextMessageEventContent,
@@ -36,14 +37,13 @@ use matrix_sdk::{
             tag::TagName,
             AnyRoomAccountDataEvent, AnyStateEventContent, AnyStrippedStateEvent,
             AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType, SyncMessageEvent,
-            SyncStateEvent, Unsigned,
+            Unsigned,
         },
         identifiers::{EventId, RoomId, UserId},
         serde::Raw,
         MilliSecondsSinceUnixEpoch,
     },
     uuid::Uuid,
-    RoomMember,
 };
 use serde_json::value::RawValue;
 use std::cell::RefCell;
@@ -53,6 +53,7 @@ use std::path::PathBuf;
 use crate::components::{LabelWithWidgets, Pill};
 use crate::prelude::*;
 use crate::session::avatar::update_room_avatar_from_file;
+use crate::session::room::member_list::MemberList;
 use crate::session::{Avatar, Session};
 use crate::Error;
 use crate::{spawn, spawn_tokio};
@@ -63,7 +64,6 @@ mod imp {
     use glib::subclass::Signal;
     use once_cell::{sync::Lazy, unsync::OnceCell};
     use std::cell::Cell;
-    use std::collections::HashMap;
 
     #[derive(Debug, Default)]
     pub struct Room {
@@ -74,7 +74,7 @@ mod imp {
         pub avatar: OnceCell<Avatar>,
         pub category: Cell<RoomType>,
         pub timeline: OnceCell<Timeline>,
-        pub room_members: RefCell<HashMap<UserId, Member>>,
+        pub members: OnceCell<MemberList>,
         /// The user who sent the invite to this room. This is only set when this room is an invitiation.
         pub inviter: RefCell<Option<Member>>,
         pub members_loaded: Cell<bool>,
@@ -174,6 +174,13 @@ mod imp {
                         glib::DateTime::static_type(),
                         glib::ParamFlags::READABLE,
                     ),
+                    glib::ParamSpec::new_object(
+                        "members",
+                        "Members",
+                        "Model of the room’s members",
+                        MemberList::static_type(),
+                        glib::ParamFlags::READABLE,
+                    ),
                 ]
             });
 
@@ -225,6 +232,7 @@ mod imp {
                 "category" => obj.category().to_value(),
                 "highlight" => obj.highlight().to_value(),
                 "topic" => obj.topic().to_value(),
+                "members" => obj.members().to_value(),
                 "notification-count" => {
                     let highlight = matrix_room.unread_notification_counts().highlight_count;
                     let notification = matrix_room.unread_notification_counts().notification_count;
@@ -253,6 +261,7 @@ mod imp {
 
             obj.set_matrix_room(obj.session().client().get_room(obj.room_id()).unwrap());
             self.timeline.set(Timeline::new(obj)).unwrap();
+            self.members.set(MemberList::new(obj)).unwrap();
             self.avatar
                 .set(Avatar::new(&obj.session(), obj.matrix_room().avatar_url()))
                 .unwrap();
@@ -479,6 +488,11 @@ impl Room {
         priv_.timeline.get().unwrap()
     }
 
+    pub fn members(&self) -> &MemberList {
+        let priv_ = imp::Room::from_instance(self);
+        priv_.members.get().unwrap()
+    }
+
     fn notify_notification_count(&self) {
         self.notify("highlight");
         self.notify("notification-count");
@@ -624,19 +638,6 @@ impl Room {
         priv_.inviter.borrow().clone()
     }
 
-    /// Returns the room member `User` object
-    ///
-    /// The returned `User` is specific to this room
-    pub fn member_by_id(&self, user_id: &UserId) -> Member {
-        let priv_ = imp::Room::from_instance(self);
-        let mut room_members = priv_.room_members.borrow_mut();
-
-        room_members
-            .entry(user_id.clone())
-            .or_insert_with(|| Member::new(self, user_id))
-            .clone()
-    }
-
     /// Handle stripped state events.
     ///
     /// Events passed to this function aren't added to the timeline.
@@ -684,7 +685,7 @@ impl Room {
         for event in batch.iter().flat_map(Event::matrix_event) {
             match &event {
                 AnySyncRoomEvent::State(AnySyncStateEvent::RoomMember(event)) => {
-                    self.update_member_for_member_event(event)
+                    self.members().update_member_for_member_event(event)
                 }
                 AnySyncRoomEvent::State(AnySyncStateEvent::RoomAvatar(event)) => {
                     self.avatar().set_url(event.content.url.to_owned());
@@ -721,32 +722,6 @@ impl Room {
         priv_.latest_change.borrow().clone()
     }
 
-    /// Add an initial set of members needed to display room events
-    ///
-    /// The `Timeline` makes sure to update the members when a member state event arrives
-    fn add_members(&self, members: Vec<RoomMember>) {
-        let priv_ = imp::Room::from_instance(self);
-        let mut room_members = priv_.room_members.borrow_mut();
-        for member in members {
-            let user_id = member.user_id();
-            let user = room_members
-                .entry(user_id.clone())
-                .or_insert_with(|| Member::new(self, user_id));
-            user.update_from_room_member(&member);
-        }
-    }
-
-    /// Updates a room member based on the room member state event
-    fn update_member_for_member_event(&self, event: &SyncStateEvent<RoomMemberEventContent>) {
-        let priv_ = imp::Room::from_instance(self);
-        let mut room_members = priv_.room_members.borrow_mut();
-        let user_id = &event.sender;
-        let user = room_members
-            .entry(user_id.clone())
-            .or_insert_with(|| Member::new(self, user_id));
-        user.update_from_member_event(event);
-    }
-
     pub fn load_members(&self) {
         let priv_ = imp::Room::from_instance(self);
         if priv_.members_loaded.get() {
@@ -762,11 +737,14 @@ impl Room {
                 // FIXME: We should retry to load the room members if the request failed
                 let priv_ = imp::Room::from_instance(&obj);
                 match handle.await.unwrap() {
-                        Ok(members) => obj.add_members(members),
-                        Err(error) => {
-                            priv_.members_loaded.set(false);
-                            error!("Couldn’t load room members: {}", error)
-                        },
+                    Ok(members) => {
+                        // Add all members needed to display room events.
+                        obj.members().update_from_room_members(&members);
+                    },
+                    Err(error) => {
+                        priv_.members_loaded.set(false);
+                        error!("Couldn’t load room members: {}", error)
+                    },
                 };
             })
         );
@@ -867,7 +845,7 @@ impl Room {
     pub fn new_allowed_expr(&self, room_action: RoomAction) -> gtk::Expression {
         let session = self.session();
         let user_id = session.user().unwrap().user_id();
-        let member = self.member_by_id(user_id);
+        let member = self.members().member_by_id(user_id);
         self.power_levels().new_allowed_expr(&member, room_action)
     }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]