[fractal/fractal-next] room: Add PowerLevels GObject



commit aa649a89b0f5933258c98cd72ab0b0f909472c8b
Author: Kai A. Hiller <V02460 gmail com>
Date:   Thu Aug 12 15:51:22 2021 +0200

    room: Add PowerLevels GObject

 src/meson.build                  |   1 +
 src/session/room/mod.rs          |   2 +
 src/session/room/power_levels.rs | 141 +++++++++++++++++++++++++++++++++++++++
 src/session/room/room.rs         |  20 +++++-
 src/utils.rs                     |   9 ++-
 5 files changed, 171 insertions(+), 2 deletions(-)
---
diff --git a/src/meson.build b/src/meson.build
index 032fed6d..ee7f4d2a 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -58,6 +58,7 @@ sources = files(
   'session/room/item.rs',
   'session/room/member.rs',
   'session/room/mod.rs',
+  'session/room/power_levels.rs',
   'session/room/room.rs',
   'session/room/room_type.rs',
   'session/room_list.rs',
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index fa8d4f82..c6920c6d 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 power_levels;
 mod room;
 mod room_type;
 mod timeline;
@@ -11,6 +12,7 @@ pub use self::highlight_flags::HighlightFlags;
 pub use self::item::Item;
 pub use self::item::ItemType;
 pub use self::member::Member;
+pub use self::power_levels::{PowerLevels, RoomAction};
 pub use self::room::Room;
 pub use self::room_type::RoomType;
 pub use self::timeline::Timeline;
diff --git a/src/session/room/power_levels.rs b/src/session/room/power_levels.rs
new file mode 100644
index 00000000..4bd3869d
--- /dev/null
+++ b/src/session/room/power_levels.rs
@@ -0,0 +1,141 @@
+use gtk::glib;
+use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use matrix_sdk::ruma::events::room::power_levels::PowerLevelsEventContent;
+use matrix_sdk::ruma::events::{EventType, SyncStateEvent};
+
+use crate::session::room::Member;
+use crate::utils::prop_expr;
+
+#[derive(Clone, Debug, Default, glib::GBoxed)]
+#[gboxed(type_name = "BoxedPowerLevelsEventContent")]
+pub struct BoxedPowerLevelsEventContent(PowerLevelsEventContent);
+
+mod imp {
+    use super::*;
+    use once_cell::sync::Lazy;
+    use std::cell::RefCell;
+
+    #[derive(Debug, Default)]
+    pub struct PowerLevels {
+        pub content: RefCell<BoxedPowerLevelsEventContent>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for PowerLevels {
+        const NAME: &'static str = "PowerLevels";
+        type Type = super::PowerLevels;
+        type ParentType = glib::Object;
+    }
+
+    impl ObjectImpl for PowerLevels {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![glib::ParamSpec::new_boxed(
+                    "power-levels",
+                    "Power levels",
+                    "Ruma struct containing all power level information of a room",
+                    BoxedPowerLevelsEventContent::static_type(),
+                    glib::ParamFlags::READABLE,
+                )]
+            });
+
+            PROPERTIES.as_ref()
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "power-levels" => obj.power_levels().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+    }
+}
+
+glib::wrapper! {
+    pub struct PowerLevels(ObjectSubclass<imp::PowerLevels>);
+}
+
+impl PowerLevels {
+    pub fn new() -> Self {
+        glib::Object::new(&[]).expect("Failed to create PowerLevels")
+    }
+
+    pub fn power_levels(&self) -> BoxedPowerLevelsEventContent {
+        let priv_ = imp::PowerLevels::from_instance(self);
+        priv_.content.borrow().clone()
+    }
+
+    /// Returns the power level minimally required to perform the given action.
+    pub fn min_level_for_room_action(&self, room_action: &RoomAction) -> u32 {
+        let priv_ = imp::PowerLevels::from_instance(self);
+        let content = priv_.content.borrow();
+        min_level_for_room_action(&content.0, room_action)
+    }
+
+    /// Creates an expression that is true when the user is allowed the given action.
+    pub fn new_allowed_expr(&self, member: &Member, room_action: RoomAction) -> gtk::Expression {
+        gtk::ClosureExpression::new(
+            move |args| {
+                let power_level: u32 = args[1].get().unwrap();
+                let content = args[2].get::<BoxedPowerLevelsEventContent>().unwrap().0;
+                power_level >= min_level_for_room_action(&content, &room_action)
+            },
+            &[
+                prop_expr(member, "power-level"),
+                prop_expr(self, "power-levels"),
+            ],
+        )
+        .upcast()
+    }
+
+    /// Updates the power levels from the given event.
+    pub fn update_from_event(&self, event: SyncStateEvent<PowerLevelsEventContent>) {
+        let priv_ = imp::PowerLevels::from_instance(self);
+        let content = BoxedPowerLevelsEventContent(event.content);
+        priv_.content.replace(content);
+        self.notify("power-levels");
+    }
+}
+
+impl Default for PowerLevels {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+/// Returns the power level minimally required to perform the given action.
+fn min_level_for_room_action(content: &PowerLevelsEventContent, room_action: &RoomAction) -> u32 {
+    let power_level = i64::from(match room_action {
+        RoomAction::Ban => content.ban,
+        RoomAction::Invite => content.invite,
+        RoomAction::Kick => content.kick,
+        RoomAction::Redact => content.redact,
+        RoomAction::RoomNotification => content.notifications.room,
+        RoomAction::StateEvent(event_type) => *content
+            .events
+            .get(event_type)
+            .unwrap_or(&content.state_default),
+        RoomAction::MessageEvent(event_type) => *content
+            .events
+            .get(event_type)
+            .unwrap_or(&content.events_default),
+    });
+
+    if (0..=100).contains(&power_level) {
+        power_level as u32
+    } else {
+        0
+    }
+}
+
+/// Actions that require different power levels to perform them.
+pub enum RoomAction {
+    Ban,
+    Invite,
+    Kick,
+    Redact,
+    RoomNotification,
+    StateEvent(EventType),
+    MessageEvent(EventType),
+}
diff --git a/src/session/room/room.rs b/src/session/room/room.rs
index 3538987e..346db85b 100644
--- a/src/session/room/room.rs
+++ b/src/session/room/room.rs
@@ -31,7 +31,9 @@ use std::convert::TryFrom;
 
 use crate::components::{LabelWithWidgets, Pill};
 use crate::prelude::*;
-use crate::session::room::{Event, HighlightFlags, Member, RoomType, Timeline};
+use crate::session::room::{
+    Event, HighlightFlags, Member, PowerLevels, RoomAction, RoomType, Timeline,
+};
 use crate::session::{Avatar, Session};
 use crate::utils::do_async;
 use crate::Error;
@@ -56,6 +58,7 @@ mod imp {
         /// 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>,
+        pub power_levels: RefCell<PowerLevels>,
     }
 
     #[glib::object_subclass]
@@ -486,6 +489,11 @@ impl Room {
             .filter(|topic| !topic.is_empty() && topic.find(|c: char| !c.is_whitespace()).is_some())
     }
 
+    pub fn power_levels(&self) -> PowerLevels {
+        let priv_ = imp::Room::from_instance(self);
+        priv_.power_levels.borrow().clone()
+    }
+
     pub fn inviter(&self) -> Option<Member> {
         let priv_ = imp::Room::from_instance(self);
         priv_.inviter.borrow().clone()
@@ -563,6 +571,9 @@ impl Room {
                     AnySyncRoomEvent::State(AnySyncStateEvent::RoomTopic(_)) => {
                         self.notify("topic");
                     }
+                    AnySyncRoomEvent::State(AnySyncStateEvent::RoomPowerLevels(event)) => {
+                        self.power_levels().update_from_event(event);
+                    }
                     _ => {}
                 }
             }
@@ -695,6 +706,13 @@ impl Room {
         }
     }
 
+    /// Creates an expression that is true when the user is allowed the given action.
+    pub fn new_allowed_expr(&self, room_action: RoomAction) -> gtk::Expression {
+        let user_id = self.session().user().user_id();
+        let member = self.member_by_id(user_id);
+        self.power_levels().new_allowed_expr(&member, room_action)
+    }
+
     pub async fn accept_invite(&self) -> Result<(), Error> {
         let matrix_room = self.matrix_room();
 
diff --git a/src/utils.rs b/src/utils.rs
index 6642b57e..7b5e8d3b 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -33,7 +33,8 @@ macro_rules! event_from_sync_event {
 }
 
 use crate::RUNTIME;
-use gtk::glib;
+use gtk::gio::prelude::*;
+use gtk::glib::{self, Object};
 use std::future::Future;
 /// Execute a future on a tokio runtime and spawn a future on the local thread to handle the result
 pub fn do_async<
@@ -54,3 +55,9 @@ pub fn do_async<
 
     RUNTIME.spawn(async move { sender.send(tokio_fut.await) });
 }
+
+/// Returns an expression looking up the given property on `object`.
+pub fn prop_expr<T: IsA<Object>>(object: &T, prop: &str) -> gtk::Expression {
+    let obj_expr = gtk::ConstantExpression::new(object).upcast();
+    gtk::PropertyExpression::new(T::static_type(), Some(&obj_expr), prop).upcast()
+}


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