[fractal] event: Subclass events to separate those that fail to deserialize
- From: Kévin Commaille <kcommaille src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal] event: Subclass events to separate those that fail to deserialize
- Date: Fri, 19 Aug 2022 09:15:00 +0000 (UTC)
commit 36eee80e4feae39e9892ec6af41bc37d4d300bc9
Author: Kévin Commaille <zecakeh tedomum fr>
Date: Mon Jun 13 13:44:47 2022 +0200
event: Subclass events to separate those that fail to deserialize
src/prelude.rs | 5 +-
src/session/content/room_history/item_row.rs | 30 +-
.../content/room_history/message_row/mod.rs | 18 +-
src/session/content/room_history/mod.rs | 4 +-
src/session/media_viewer.rs | 12 +-
src/session/mod.rs | 4 +-
src/session/room/event/mod.rs | 419 +++++++++++++++++
.../room/{event.rs => event/supported_event.rs} | 519 ++++++++-------------
src/session/room/event/unsupported_event.rs | 56 +++
src/session/room/event_actions.rs | 227 ++++-----
src/session/room/mod.rs | 30 +-
src/session/room/reaction_group.rs | 12 +-
src/session/room/reaction_list.rs | 12 +-
src/session/room/timeline/mod.rs | 184 ++++----
src/session/room/timeline/timeline_item.rs | 26 +-
15 files changed, 970 insertions(+), 588 deletions(-)
---
diff --git a/src/prelude.rs b/src/prelude.rs
index e9c522d6a..5b22279de 100644
--- a/src/prelude.rs
+++ b/src/prelude.rs
@@ -1 +1,4 @@
-pub use crate::session::UserExt;
+pub use crate::session::{
+ room::{EventExt, TimelineItemExt},
+ UserExt,
+};
diff --git a/src/session/content/room_history/item_row.rs b/src/session/content/room_history/item_row.rs
index cd32f29e1..2711d10c5 100644
--- a/src/session/content/room_history/item_row.rs
+++ b/src/session/content/room_history/item_row.rs
@@ -5,11 +5,12 @@ use matrix_sdk::ruma::events::AnySyncRoomEvent;
use crate::{
components::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl, ReactionChooser},
+ prelude::*,
session::{
content::room_history::{message_row::MessageRow, DividerRow, RoomHistory, StateRow},
room::{
- Event, EventActions, TimelineDayDivider, TimelineItem, TimelineNewMessagesDivider,
- TimelineSpinner,
+ Event, EventActions, SupportedEvent, TimelineDayDivider, TimelineItem,
+ TimelineNewMessagesDivider, TimelineSpinner,
},
},
};
@@ -95,7 +96,7 @@ mod imp {
.item
.borrow()
.as_ref()
- .and_then(|item| item.downcast_ref::<Event>())
+ .and_then(|item| item.downcast_ref::<SupportedEvent>())
{
if let Some(handler) = self.notify_handler.borrow_mut().take() {
event.disconnect(handler);
@@ -113,7 +114,10 @@ mod imp {
let room_history = obj.room_history();
let popover = room_history.item_context_menu().to_owned();
- if event.content().is_some() {
+ if let Some(event) = event
+ .downcast_ref::<SupportedEvent>()
+ .filter(|event| event.content().is_some())
+ {
let menu_model = Self::Type::event_message_menu_model();
let reaction_chooser = room_history.item_reaction_chooser();
if popover.menu_model().as_ref() != Some(menu_model) {
@@ -189,7 +193,7 @@ impl ItemRow {
.item
.borrow()
.as_ref()
- .and_then(|item| item.downcast_ref::<Event>())
+ .and_then(|item| item.downcast_ref::<SupportedEvent>())
{
if let Some(handler) = priv_.notify_handler.borrow_mut().take() {
event.disconnect(handler);
@@ -199,15 +203,13 @@ impl ItemRow {
}
if let Some(ref item) = item {
- if let Some(event) = item.downcast_ref::<Event>() {
- self.set_action_group(self.set_event_actions(Some(event)));
+ if let Some(event) = item.downcast_ref::<SupportedEvent>() {
+ self.set_action_group(self.set_event_actions(Some(event.upcast_ref())));
- let notify_handler = event.connect_notify_local(
- Some("event"),
- clone!(@weak self as obj => move |event, _| {
+ let notify_handler =
+ event.connect_pure_event_notify(clone!(@weak self as obj => move |event| {
obj.set_event_widget(event);
- }),
- );
+ }));
priv_.notify_handler.replace(Some(notify_handler));
self.set_event_widget(event);
@@ -265,9 +267,9 @@ impl ItemRow {
priv_.item.replace(item);
}
- fn set_event_widget(&self, event: &Event) {
+ fn set_event_widget(&self, event: &SupportedEvent) {
match event.matrix_event() {
- Some(AnySyncRoomEvent::State(state)) => {
+ AnySyncRoomEvent::State(state) => {
let child = if let Some(Ok(child)) = self.child().map(|w| w.downcast::<StateRow>())
{
child
diff --git a/src/session/content/room_history/message_row/mod.rs
b/src/session/content/room_history/message_row/mod.rs
index 2804a4014..8668aaf28 100644
--- a/src/session/content/room_history/message_row/mod.rs
+++ b/src/session/content/room_history/message_row/mod.rs
@@ -25,7 +25,7 @@ use self::{
reaction_list::MessageReactionList, reply::MessageReply, text::MessageText,
};
use crate::{
- components::Avatar, prelude::*, session::room::Event, spawn, utils::filename_for_mime,
+ components::Avatar, prelude::*, session::room::SupportedEvent, spawn, utils::filename_for_mime,
};
mod imp {
@@ -53,7 +53,7 @@ mod imp {
pub reactions: TemplateChild<MessageReactionList>,
pub source_changed_handler: RefCell<Option<SignalHandlerId>>,
pub bindings: RefCell<Vec<glib::Binding>>,
- pub event: RefCell<Option<Event>>,
+ pub event: RefCell<Option<SupportedEvent>>,
}
#[glib::object_subclass]
@@ -144,7 +144,7 @@ impl MessageRow {
self.notify("show-header");
}
- pub fn set_event(&self, event: Event) {
+ pub fn set_event(&self, event: SupportedEvent) {
let priv_ = self.imp();
// Remove signals and bindings from the previous event
if let Some(event) = priv_.event.take() {
@@ -195,14 +195,20 @@ impl MessageRow {
priv_.event.replace(Some(event));
}
- fn update_content(&self, event: &Event) {
+ fn update_content(&self, event: &SupportedEvent) {
if event.is_reply() {
spawn!(
glib::PRIORITY_HIGH,
clone!(@weak self as obj, @weak event => async move {
let priv_ = obj.imp();
- if let Ok(Some(related_event)) = event.reply_to_event().await {
+ if let Some(related_event) = event
+ .reply_to_event()
+ .await
+ .ok()
+ .flatten()
+ .and_then(|event| event.downcast::<SupportedEvent>().ok())
+ {
let reply = MessageReply::new();
reply.set_related_content_sender(related_event.sender().upcast());
build_content(reply.related_content(), &related_event, true);
@@ -229,7 +235,7 @@ impl Default for MessageRow {
///
/// If `compact` is true, the content should appear in a smaller format without
/// interactions, if possible.
-fn build_content(parent: &adw::Bin, event: &Event, compact: bool) {
+fn build_content(parent: &adw::Bin, event: &SupportedEvent, compact: bool) {
match event.content() {
Some(AnyMessageLikeEventContent::RoomMessage(message)) => {
let msgtype = if let Some(Relation::Replacement(replacement)) = message.relates_to {
diff --git a/src/session/content/room_history/mod.rs b/src/session/content/room_history/mod.rs
index d2d0ca337..a4e72a43e 100644
--- a/src/session/content/room_history/mod.rs
+++ b/src/session/content/room_history/mod.rs
@@ -37,7 +37,7 @@ use crate::{
i18n::gettext_f,
session::{
content::{MarkdownPopover, RoomDetails},
- room::{Event, Room, RoomType, Timeline, TimelineItem, TimelineState},
+ room::{Room, RoomType, SupportedEvent, Timeline, TimelineItem, TimelineState},
user::UserExt,
},
spawn, toast,
@@ -280,7 +280,7 @@ mod imp {
.model()
.and_then(|model| model.item(pos))
.as_ref()
- .and_then(|o| o.downcast_ref::<Event>())
+ .and_then(|o| o.downcast_ref::<SupportedEvent>())
{
if let Some(room) = obj.room() {
room.session().show_media(event);
diff --git a/src/session/media_viewer.rs b/src/session/media_viewer.rs
index 4a5bdce3a..3f67d5560 100644
--- a/src/session/media_viewer.rs
+++ b/src/session/media_viewer.rs
@@ -6,7 +6,7 @@ use matrix_sdk::ruma::events::{room::message::MessageType, AnyMessageLikeEventCo
use super::room::EventActions;
use crate::{
components::{ContentType, MediaContentViewer},
- session::room::Event,
+ session::room::SupportedEvent,
spawn,
utils::cache_dir,
Window,
@@ -24,7 +24,7 @@ mod imp {
#[template(resource = "/org/gnome/Fractal/media-viewer.ui")]
pub struct MediaViewer {
pub fullscreened: Cell<bool>,
- pub event: RefCell<Option<WeakRef<Event>>>,
+ pub event: RefCell<Option<WeakRef<SupportedEvent>>>,
pub body: RefCell<Option<String>>,
#[template_child]
pub flap: TemplateChild<adw::Flap>,
@@ -91,7 +91,7 @@ mod imp {
"event",
"Event",
"The media event to display",
- Event::static_type(),
+ SupportedEvent::static_type(),
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
),
glib::ParamSpecString::new(
@@ -164,7 +164,7 @@ impl MediaViewer {
glib::Object::new(&[]).expect("Failed to create MediaViewer")
}
- pub fn event(&self) -> Option<Event> {
+ pub fn event(&self) -> Option<SupportedEvent> {
self.imp()
.event
.borrow()
@@ -172,7 +172,7 @@ impl MediaViewer {
.and_then(|event| event.upgrade())
}
- pub fn set_event(&self, event: Option<Event>) {
+ pub fn set_event(&self, event: Option<SupportedEvent>) {
if event == self.event() {
return;
}
@@ -226,7 +226,7 @@ impl MediaViewer {
self.imp().media.show_loading();
if let Some(event) = self.event() {
- self.set_event_actions(Some(&event));
+ self.set_event_actions(Some(event.upcast_ref()));
if let Some(AnyMessageLikeEventContent::RoomMessage(content)) = event.content() {
match content.msgtype {
MessageType::Image(image) => {
diff --git a/src/session/mod.rs b/src/session/mod.rs
index b3e17d58d..d26c049b7 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -63,7 +63,7 @@ use self::{
};
pub use self::{
avatar::Avatar,
- room::{Event, Room},
+ room::{Room, SupportedEvent},
room_creation::RoomCreation,
user::{User, UserActions, UserExt},
};
@@ -896,7 +896,7 @@ impl Session {
}
/// Show a media event
- pub fn show_media(&self, event: &Event) {
+ pub fn show_media(&self, event: &SupportedEvent) {
let priv_ = self.imp();
priv_.media_viewer.set_event(Some(event.clone()));
diff --git a/src/session/room/event/mod.rs b/src/session/room/event/mod.rs
new file mode 100644
index 000000000..004cc3441
--- /dev/null
+++ b/src/session/room/event/mod.rs
@@ -0,0 +1,419 @@
+use gtk::{glib, prelude::*, subclass::prelude::*};
+use log::warn;
+use matrix_sdk::{
+ deserialized_responses::SyncRoomEvent,
+ ruma::{MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId},
+};
+
+use super::{
+ timeline::{TimelineItem, TimelineItemImpl},
+ Member, Room,
+};
+
+mod supported_event;
+mod unsupported_event;
+
+pub use supported_event::SupportedEvent;
+pub use unsupported_event::UnsupportedEvent;
+
+#[derive(Clone, Debug, glib::Boxed)]
+#[boxed_type(name = "BoxedSyncRoomEvent")]
+pub struct BoxedSyncRoomEvent(SyncRoomEvent);
+
+mod imp {
+ use std::cell::RefCell;
+
+ use glib::{object::WeakRef, Class};
+ use once_cell::{sync::Lazy, unsync::OnceCell};
+
+ use super::*;
+
+ #[repr(C)]
+ pub struct EventClass {
+ pub parent_class: Class<TimelineItem>,
+ pub source: fn(&super::Event) -> String,
+ pub event_id: fn(&super::Event) -> Option<OwnedEventId>,
+ pub sender_id: fn(&super::Event) -> Option<OwnedUserId>,
+ pub origin_server_ts: fn(&super::Event) -> Option<MilliSecondsSinceUnixEpoch>,
+ }
+
+ unsafe impl ClassStruct for EventClass {
+ type Type = Event;
+ }
+
+ pub(super) fn event_source(this: &super::Event) -> String {
+ let klass = this.class();
+ (klass.as_ref().source)(this)
+ }
+
+ pub(super) fn event_event_id(this: &super::Event) -> Option<OwnedEventId> {
+ let klass = this.class();
+ (klass.as_ref().event_id)(this)
+ }
+
+ pub(super) fn event_sender_id(this: &super::Event) -> Option<OwnedUserId> {
+ let klass = this.class();
+ (klass.as_ref().sender_id)(this)
+ }
+
+ pub(super) fn event_origin_server_ts(
+ this: &super::Event,
+ ) -> Option<MilliSecondsSinceUnixEpoch> {
+ let klass = this.class();
+ (klass.as_ref().origin_server_ts)(this)
+ }
+
+ #[derive(Debug, Default)]
+ pub struct Event {
+ /// The SDK event containing encryption information and the serialized
+ /// event as `Raw`.
+ pub pure_event: RefCell<Option<SyncRoomEvent>>,
+
+ /// The room containing this `Event`.
+ pub room: OnceCell<WeakRef<Room>>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for Event {
+ const NAME: &'static str = "RoomEvent";
+ const ABSTRACT: bool = true;
+ type Type = super::Event;
+ type ParentType = TimelineItem;
+ type Class = EventClass;
+ }
+
+ impl ObjectImpl for Event {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![
+ glib::ParamSpecBoxed::new(
+ "pure-event",
+ "Pure Event",
+ "The pure Matrix event of this Event",
+ BoxedSyncRoomEvent::static_type(),
+ glib::ParamFlags::WRITABLE,
+ ),
+ glib::ParamSpecString::new(
+ "source",
+ "Source",
+ "The JSON source of this Event",
+ None,
+ glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ ),
+ glib::ParamSpecObject::new(
+ "room",
+ "Room",
+ "The room containing this Event",
+ Room::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+ ),
+ glib::ParamSpecString::new(
+ "time",
+ "Time",
+ "The locally formatted time of this Matrix event",
+ None,
+ glib::ParamFlags::READABLE,
+ ),
+ ]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "pure-event" => {
+ let event = value.get::<BoxedSyncRoomEvent>().unwrap();
+ obj.set_pure_event(event.0);
+ }
+ "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() {
+ "source" => obj.source().to_value(),
+ "room" => obj.room().to_value(),
+ "time" => obj.time().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+ }
+
+ impl TimelineItemImpl for Event {
+ fn event_sender(&self, obj: &Self::Type) -> Option<Member> {
+ Some(obj.room().members().member_by_id(obj.sender_id()?))
+ }
+
+ fn selectable(&self, _obj: &Self::Type) -> bool {
+ true
+ }
+ }
+}
+
+glib::wrapper! {
+ /// GObject representation of a Matrix room event.
+ pub struct Event(ObjectSubclass<imp::Event>) @extends TimelineItem;
+}
+
+impl Event {
+ /// Create an `Event` with the given pure SDK event and room.
+ ///
+ /// Constructs the proper subtype according to the event.
+ pub fn new(pure_event: SyncRoomEvent, room: &Room) -> Self {
+ SupportedEvent::try_from_event(pure_event.clone(), room)
+ .map(|event| event.upcast())
+ .unwrap_or_else(|_| {
+ warn!("Failed to deserialize event: {:?}", pure_event);
+ UnsupportedEvent::new(pure_event, room).upcast()
+ })
+ }
+}
+
+/// Public trait containing implemented methods for everything that derives from
+/// `Event`.
+///
+/// To override the behavior of these methods, override the corresponding method
+/// of `EventImpl`.
+pub trait EventExt: 'static {
+ /// The `Room` where this `Event` was sent.
+ fn room(&self) -> Room;
+
+ /// The pure SDK event of this `Event`.
+ fn pure_event(&self) -> SyncRoomEvent;
+
+ /// Set the pure SDK event of this `Event`.
+ fn set_pure_event(&self, pure_event: SyncRoomEvent);
+
+ /// The source JSON of this `Event`.
+ fn original_source(&self) -> String;
+
+ /// The source JSON displayed for this `Event`.
+ ///
+ /// Defaults to the `original_source`.
+ fn source(&self) -> String;
+
+ /// The event ID of this `Event`, if it was found.
+ fn event_id(&self) -> Option<OwnedEventId>;
+
+ /// The user ID of the sender of this `Event`, if it was found.
+ fn sender_id(&self) -> Option<OwnedUserId>;
+
+ /// The timestamp on the origin server when this `Event` was sent as
+ /// `MilliSecondsSinceUnixEpoch`, if it was found.
+ fn origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch>;
+
+ /// The timestamp on the origin server when this `Event` was sent as
+ /// `glib::DateTime`.
+ ///
+ /// This is computed from the `origin_server_ts`.
+ fn timestamp(&self) -> Option<glib::DateTime> {
+ glib::DateTime::from_unix_utc(self.origin_server_ts()?.as_secs().into())
+ .and_then(|t| t.to_local())
+ .ok()
+ }
+
+ /// The formatted time when this `Event` was sent.
+ ///
+ /// This is computed from the `origin_server_ts`.
+ fn time(&self) -> Option<String> {
+ let datetime = self.timestamp()?;
+
+ // FIXME Is there a cleaner to find out if we should use 24h format?
+ let local_time = datetime.format("%X").unwrap().as_str().to_ascii_lowercase();
+
+ let time = if local_time.ends_with("am") || local_time.ends_with("pm") {
+ // Use 12h time format (AM/PM)
+ datetime.format("%l∶%M %p").unwrap().to_string()
+ } else {
+ // Use 24 time format
+ datetime.format("%R").unwrap().to_string()
+ };
+ Some(time)
+ }
+
+ fn connect_pure_event_notify<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId;
+}
+
+impl<O: IsA<Event>> EventExt for O {
+ fn room(&self) -> Room {
+ self.upcast_ref()
+ .imp()
+ .room
+ .get()
+ .unwrap()
+ .upgrade()
+ .unwrap()
+ }
+
+ fn pure_event(&self) -> SyncRoomEvent {
+ self.upcast_ref().imp().pure_event.borrow().clone().unwrap()
+ }
+
+ fn set_pure_event(&self, pure_event: SyncRoomEvent) {
+ let priv_ = self.upcast_ref().imp();
+ priv_.pure_event.replace(Some(pure_event));
+
+ self.notify("pure-event");
+ self.notify("source");
+ }
+
+ fn original_source(&self) -> String {
+ let pure_event = self.upcast_ref().imp().pure_event.borrow();
+ let raw = pure_event.as_ref().unwrap().event.json().get();
+
+ // We have to convert it to a Value, because a RawValue cannot be
+ // pretty-printed.
+ if let Ok(json) = serde_json::from_str::<serde_json::Value>(raw) {
+ serde_json::to_string_pretty(&json).unwrap()
+ } else {
+ raw.to_owned()
+ }
+ }
+
+ fn source(&self) -> String {
+ imp::event_source(self.upcast_ref())
+ }
+
+ fn event_id(&self) -> Option<OwnedEventId> {
+ imp::event_event_id(self.upcast_ref())
+ }
+
+ fn sender_id(&self) -> Option<OwnedUserId> {
+ imp::event_sender_id(self.upcast_ref())
+ }
+
+ fn origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch> {
+ imp::event_origin_server_ts(self.upcast_ref())
+ }
+
+ fn connect_pure_event_notify<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
+ self.connect_notify_local(Some("pure-event"), move |this, _| {
+ f(this);
+ })
+ }
+}
+
+/// Public trait that must be implemented for everything that derives from
+/// `Event`.
+///
+/// Overriding a method from this trait overrides also its behavior in
+/// `EventExt`.
+pub trait EventImpl: ObjectImpl {
+ fn source(&self, obj: &Self::Type) -> String {
+ obj.dynamic_cast_ref::<Event>()
+ .map(|event| event.original_source())
+ .unwrap_or_default()
+ }
+
+ fn event_id(&self, obj: &Self::Type) -> Option<OwnedEventId> {
+ obj.dynamic_cast_ref::<Event>().and_then(|event| {
+ event
+ .imp()
+ .pure_event
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .event
+ .get_field::<OwnedEventId>("event_id")
+ .ok()
+ .flatten()
+ })
+ }
+
+ fn sender_id(&self, obj: &Self::Type) -> Option<OwnedUserId> {
+ obj.dynamic_cast_ref::<Event>().and_then(|event| {
+ event
+ .imp()
+ .pure_event
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .event
+ .get_field::<OwnedUserId>("sender")
+ .ok()
+ .flatten()
+ })
+ }
+
+ fn origin_server_ts(&self, obj: &Self::Type) -> Option<MilliSecondsSinceUnixEpoch> {
+ obj.dynamic_cast_ref::<Event>().and_then(|event| {
+ event
+ .imp()
+ .pure_event
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .event
+ .get_field::<MilliSecondsSinceUnixEpoch>("origin_server_ts")
+ .ok()
+ .flatten()
+ })
+ }
+}
+
+// Make `Event` subclassable.
+unsafe impl<T> IsSubclassable<T> for Event
+where
+ T: TimelineItemImpl + EventImpl,
+ T::Type: IsA<TimelineItem> + IsA<Event>,
+{
+ fn class_init(class: &mut glib::Class<Self>) {
+ Self::parent_class_init::<T>(class.upcast_ref_mut());
+
+ let klass = class.as_mut();
+
+ klass.source = source_trampoline::<T>;
+ klass.event_id = event_id_trampoline::<T>;
+ klass.sender_id = sender_id_trampoline::<T>;
+ klass.origin_server_ts = origin_server_ts_trampoline::<T>;
+ }
+}
+
+// Virtual method implementation trampolines.
+fn source_trampoline<T>(this: &Event) -> String
+where
+ T: ObjectSubclass + EventImpl,
+ T::Type: IsA<Event>,
+{
+ let this = this.downcast_ref::<T::Type>().unwrap();
+ this.imp().source(this)
+}
+
+fn event_id_trampoline<T>(this: &Event) -> Option<OwnedEventId>
+where
+ T: ObjectSubclass + EventImpl,
+ T::Type: IsA<Event>,
+{
+ let this = this.downcast_ref::<T::Type>().unwrap();
+ this.imp().event_id(this)
+}
+
+fn sender_id_trampoline<T>(this: &Event) -> Option<OwnedUserId>
+where
+ T: ObjectSubclass + EventImpl,
+ T::Type: IsA<Event>,
+{
+ let this = this.downcast_ref::<T::Type>().unwrap();
+ this.imp().sender_id(this)
+}
+
+fn origin_server_ts_trampoline<T>(this: &Event) -> Option<MilliSecondsSinceUnixEpoch>
+where
+ T: ObjectSubclass + EventImpl,
+ T::Type: IsA<Event>,
+{
+ let this = this.downcast_ref::<T::Type>().unwrap();
+ this.imp().origin_server_ts(this)
+}
diff --git a/src/session/room/event.rs b/src/session/room/event/supported_event.rs
similarity index 58%
rename from src/session/room/event.rs
rename to src/session/room/event/supported_event.rs
index 5d91b1339..e5496831e 100644
--- a/src/session/room/event.rs
+++ b/src/session/room/event/supported_event.rs
@@ -1,9 +1,4 @@
-use gtk::{
- glib,
- glib::{clone, DateTime},
- prelude::*,
- subclass::prelude::*,
-};
+use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
use log::warn;
use matrix_sdk::{
deserialized_responses::SyncRoomEvent,
@@ -16,89 +11,67 @@ use matrix_sdk::{
redaction::SyncRoomRedactionEvent,
},
AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncRoomEvent,
- AnySyncStateEvent, MessageLikeUnsigned, SyncMessageLikeEvent, SyncStateEvent,
+ AnySyncStateEvent, SyncMessageLikeEvent, SyncStateEvent,
},
serde::Raw,
MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
},
Error as MatrixError,
};
+use serde_json::Error as JsonError;
-use super::{
- timeline::{TimelineItem, TimelineItemImpl},
- Member, ReactionList, Room,
-};
+use super::{BoxedSyncRoomEvent, Event, EventImpl};
use crate::{
+ prelude::*,
+ session::room::{
+ timeline::{TimelineItem, TimelineItemImpl},
+ Member, ReactionList, Room,
+ },
spawn, spawn_tokio,
utils::{filename_for_mime, media_type_uid},
};
#[derive(Clone, Debug, glib::Boxed)]
-#[boxed_type(name = "BoxedSyncRoomEvent")]
-pub struct BoxedSyncRoomEvent(SyncRoomEvent);
+#[boxed_type(name = "BoxedAnySyncRoomEvent")]
+pub struct BoxedAnySyncRoomEvent(AnySyncRoomEvent);
mod imp {
use std::cell::RefCell;
- use glib::{object::WeakRef, SignalHandlerId};
- use once_cell::{sync::Lazy, unsync::OnceCell};
+ use glib::SignalHandlerId;
+ use once_cell::sync::Lazy;
use super::*;
#[derive(Debug, Default)]
- pub struct Event {
- /// The deserialized matrix event
- pub event: RefCell<Option<AnySyncRoomEvent>>,
- /// The SDK event containing encryption information and the serialized
- /// event as `Raw`
- pub pure_event: RefCell<Option<SyncRoomEvent>>,
+ pub struct SupportedEvent {
+ /// The deserialized Matrix event.
+ pub matrix_event: RefCell<Option<AnySyncRoomEvent>>,
/// Events that replace this one, in the order they arrive.
- pub replacing_events: RefCell<Vec<super::Event>>,
+ pub replacing_events: RefCell<Vec<super::SupportedEvent>>,
pub reactions: ReactionList,
- pub source_changed_handler: RefCell<Option<SignalHandlerId>>,
pub keys_handle: RefCell<Option<SignalHandlerId>>,
- pub room: OnceCell<WeakRef<Room>>,
+ pub source_changed_handler: RefCell<Option<SignalHandlerId>>,
}
#[glib::object_subclass]
- impl ObjectSubclass for Event {
- const NAME: &'static str = "RoomEvent";
- type Type = super::Event;
- type ParentType = TimelineItem;
+ impl ObjectSubclass for SupportedEvent {
+ const NAME: &'static str = "RoomSupportedEvent";
+ type Type = super::SupportedEvent;
+ type ParentType = Event;
}
- impl ObjectImpl for Event {
+ impl ObjectImpl for SupportedEvent {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecBoxed::new(
- "event",
- "event",
- "The matrix event of this Event",
- BoxedSyncRoomEvent::static_type(),
+ "matrix-event",
+ "Matrix Event",
+ "The deserialized Matrix event of this Event",
+ BoxedAnySyncRoomEvent::static_type(),
glib::ParamFlags::WRITABLE,
),
- glib::ParamSpecString::new(
- "source",
- "Source",
- "The source (JSON) of this Event",
- None,
- glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
- ),
- glib::ParamSpecObject::new(
- "room",
- "Room",
- "The room containing this event",
- Room::static_type(),
- glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
- ),
- glib::ParamSpecString::new(
- "time",
- "Time",
- "The locally formatted time of this matrix event",
- None,
- glib::ParamFlags::READABLE,
- ),
glib::ParamSpecObject::new(
"reactions",
"Reactions",
@@ -120,14 +93,9 @@ mod imp {
pspec: &glib::ParamSpec,
) {
match pspec.name() {
- "event" => {
- let event = value.get::<BoxedSyncRoomEvent>().unwrap();
- obj.set_matrix_pure_event(event.0);
- }
- "room" => {
- self.room
- .set(value.get::<Room>().unwrap().downgrade())
- .unwrap();
+ "matrix-event" => {
+ let matrix_event = value.get::<BoxedAnySyncRoomEvent>().unwrap();
+ obj.set_matrix_event(matrix_event.0);
}
_ => unimplemented!(),
}
@@ -135,20 +103,13 @@ mod imp {
fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
- "source" => obj.source().to_value(),
- "room" => obj.room().to_value(),
- "time" => obj.time().to_value(),
"reactions" => obj.reactions().to_value(),
_ => unimplemented!(),
}
}
}
- impl TimelineItemImpl for Event {
- fn selectable(&self, _obj: &Self::Type) -> bool {
- true
- }
-
+ impl TimelineItemImpl for SupportedEvent {
fn activatable(&self, obj: &Self::Type) -> bool {
match obj.original_content() {
// The event can be activated to open the media viewer if it's an image or a video.
@@ -181,72 +142,79 @@ mod imp {
}
}
- fn sender(&self, obj: &Self::Type) -> Option<Member> {
- Some(obj.room().members().member_by_id(obj.matrix_sender()))
+ fn event_sender(&self, obj: &Self::Type) -> Option<Member> {
+ Some(obj.sender())
+ }
+ }
+
+ impl EventImpl for SupportedEvent {
+ fn source(&self, obj: &Self::Type) -> String {
+ obj.replacement()
+ .map(|replacement| replacement.source())
+ .unwrap_or_else(|| obj.original_source())
+ }
+
+ fn origin_server_ts(&self, _obj: &Self::Type) -> Option<MilliSecondsSinceUnixEpoch> {
+ Some(
+ self.matrix_event
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .origin_server_ts(),
+ )
}
}
}
glib::wrapper! {
- /// GObject representation of a Matrix room event.
- pub struct Event(ObjectSubclass<imp::Event>) @extends TimelineItem;
+ /// GObject representation of a supported Matrix room event.
+ pub struct SupportedEvent(ObjectSubclass<imp::SupportedEvent>) @extends TimelineItem, Event;
}
// TODO:
-// - [ ] implement operations for events: forward, reply, delete...
-
-impl Event {
- pub fn new(event: SyncRoomEvent, room: &Room) -> Self {
- let event = BoxedSyncRoomEvent(event);
- glib::Object::new(&[("event", &event), ("room", room)]).expect("Failed to create Event")
- }
-
- pub fn sender(&self) -> Member {
- self.room().members().member_by_id(self.matrix_sender())
- }
-
- pub fn room(&self) -> Room {
- self.imp().room.get().unwrap().upgrade().unwrap()
- }
+// - [ ] implement operations for events: forward, reply, edit...
- /// Get the matrix event
+impl SupportedEvent {
+ /// Try to construct a new `SupportedEvent` with the given pure event and
+ /// room.
///
- /// If the `SyncRoomEvent` couldn't be deserialized this is `None`
- pub fn matrix_event(&self) -> Option<AnySyncRoomEvent> {
- self.imp().event.borrow().clone()
- }
-
- pub fn matrix_pure_event(&self) -> SyncRoomEvent {
- self.imp().pure_event.borrow().clone().unwrap()
- }
-
- pub fn set_matrix_pure_event(&self, event: SyncRoomEvent) {
- let priv_ = self.imp();
-
- if let Ok(deserialized) = event.event.deserialize() {
- if let AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
- SyncMessageLikeEvent::Original(_),
- )) = deserialized
- {
- let raw_event = event.event.clone();
- spawn!(clone!(@weak self as obj => async move {
- obj.try_to_decrypt(raw_event.cast()).await;
- }));
- }
-
- priv_.event.replace(Some(deserialized));
- } else {
- warn!("Failed to deserialize event: {:?}", event);
+ /// Returns an error if the pure event fails to deserialize.
+ pub fn try_from_event(pure_event: SyncRoomEvent, room: &Room) -> Result<Self, JsonError> {
+ let matrix_event = BoxedAnySyncRoomEvent(pure_event.event.deserialize()?);
+ let pure_event = BoxedSyncRoomEvent(pure_event);
+ Ok(glib::Object::new(&[
+ ("pure-event", &pure_event),
+ ("matrix-event", &matrix_event),
+ ("room", room),
+ ])
+ .expect("Failed to create SupportedEvent"))
+ }
+
+ /// Set the deserialized Matrix event of this `SupportedEvent`.
+ fn set_matrix_event(&self, matrix_event: AnySyncRoomEvent) {
+ if let AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
+ SyncMessageLikeEvent::Original(_),
+ )) = matrix_event
+ {
+ spawn!(clone!(@weak self as obj => async move {
+ obj.try_to_decrypt(obj.pure_event().event.cast()).await;
+ }));
}
- priv_.pure_event.replace(Some(event));
-
- self.notify("event");
+ self.imp().matrix_event.replace(Some(matrix_event));
self.notify("activatable");
- self.notify("source");
}
- async fn try_to_decrypt(&self, event: Raw<OriginalSyncRoomEncryptedEvent>) {
+ /// The deserialized Matrix event of this `SupportedEvent`.
+ pub fn matrix_event(&self) -> AnySyncRoomEvent {
+ self.imp().matrix_event.borrow().clone().unwrap()
+ }
+
+ /// Try to decrypt this `SupportedEvent` with the current room keys.
+ ///
+ /// If decryption fails, it will be retried everytime we receive new room
+ /// keys.
+ pub async fn try_to_decrypt(&self, event: Raw<OriginalSyncRoomEncryptedEvent>) {
let priv_ = self.imp();
let room = self.room().matrix_room();
let handle = spawn_tokio!(async move { room.decrypt_event(&event).await });
@@ -256,7 +224,10 @@ impl Event {
if let Some(keys_handle) = priv_.keys_handle.take() {
self.room().disconnect(keys_handle);
}
- self.set_matrix_pure_event(decrypted.into());
+ let pure_event = SyncRoomEvent::from(decrypted);
+ let matrix_event = pure_event.event.deserialize().unwrap();
+ self.set_pure_event(pure_event);
+ self.set_matrix_event(matrix_event);
}
Err(error) => {
warn!("Failed to decrypt event: {}", error);
@@ -264,7 +235,7 @@ impl Event {
let handle = self.room().connect_new_encryption_keys(
clone!(@weak self as obj => move |_| {
// Try to decrypt the event again
- obj.set_matrix_pure_event(obj.matrix_pure_event());
+ obj.set_matrix_event(obj.matrix_event());
}),
);
@@ -274,142 +245,50 @@ impl Event {
}
}
- pub fn matrix_sender(&self) -> OwnedUserId {
- let priv_ = self.imp();
-
- if let Some(event) = priv_.event.borrow().as_ref() {
- event.sender().into()
- } else {
- priv_
- .pure_event
- .borrow()
- .as_ref()
- .unwrap()
- .event
- .get_field::<OwnedUserId>("sender")
- .unwrap()
- .unwrap()
- }
- }
-
- pub fn matrix_event_id(&self) -> OwnedEventId {
- let priv_ = self.imp();
-
- if let Some(event) = priv_.event.borrow().as_ref() {
- event.event_id().to_owned()
- } else {
- priv_
- .pure_event
- .borrow()
- .as_ref()
- .unwrap()
- .event
- .get_field::<OwnedEventId>("event_id")
- .unwrap()
- .unwrap()
- }
- }
-
- pub fn matrix_transaction_id(&self) -> Option<OwnedTransactionId> {
+ /// The event ID of this `SupportedEvent`.
+ pub fn event_id(&self) -> OwnedEventId {
self.imp()
- .pure_event
+ .matrix_event
.borrow()
.as_ref()
.unwrap()
- .event
- .get_field::<MessageLikeUnsigned>("unsigned")
- .ok()
- .flatten()
- .and_then(|unsigned| unsigned.transaction_id)
+ .event_id()
+ .to_owned()
}
- /// The original timestamp of this event.
- pub fn matrix_origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
- let priv_ = self.imp();
- if let Some(event) = priv_.event.borrow().as_ref() {
- event.origin_server_ts().to_owned()
- } else {
- priv_
- .pure_event
- .borrow()
- .as_ref()
- .unwrap()
- .event
- .get_field::<MilliSecondsSinceUnixEpoch>("origin_server_ts")
- .unwrap()
- .unwrap()
- }
+ /// The user ID of the sender of this `SupportedEvent`.
+ pub fn sender_id(&self) -> OwnedUserId {
+ self.imp()
+ .matrix_event
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .sender()
+ .to_owned()
}
- /// The pretty-formatted JSON of this matrix event.
- pub fn original_source(&self) -> String {
- // We have to convert it to a Value, because a RawValue cannot be
- // pretty-printed.
- let json: serde_json::Value = serde_json::from_str(
- self.imp()
- .pure_event
- .borrow()
- .as_ref()
- .unwrap()
- .event
- .json()
- .get(),
- )
- .unwrap();
-
- serde_json::to_string_pretty(&json).unwrap()
+ /// The room member that sent this `SupportedEvent`.
+ pub fn sender(&self) -> Member {
+ self.room().members().member_by_id(self.sender_id())
}
- /// The pretty-formatted JSON used for this matrix event.
+ /// The transaction ID of this `SupportedEvent`, if any.
///
- /// If this matrix event has been replaced, returns the replacing `Event`'s
- /// source.
- pub fn source(&self) -> String {
- self.replacement()
- .map(|replacement| replacement.source())
- .unwrap_or_else(|| self.original_source())
- }
-
- pub fn timestamp(&self) -> DateTime {
- let priv_ = self.imp();
- let ts = if let Some(event) = priv_.event.borrow().as_ref() {
- event.origin_server_ts().as_secs()
- } else {
- priv_
- .pure_event
- .borrow()
- .as_ref()
- .unwrap()
- .event
- .get_field::<MilliSecondsSinceUnixEpoch>("origin_server_ts")
- .unwrap()
- .unwrap()
- .as_secs()
- };
-
- DateTime::from_unix_utc(ts.into())
- .and_then(|t| t.to_local())
+ /// This is the random string sent with the event, if it was sent from this
+ /// session.
+ pub fn transaction_id(&self) -> Option<OwnedTransactionId> {
+ self.imp()
+ .matrix_event
+ .borrow()
+ .as_ref()
.unwrap()
+ .transaction_id()
+ .map(|txn_id| txn_id.to_owned())
}
- pub fn time(&self) -> String {
- let datetime = self.timestamp();
-
- // FIXME Is there a cleaner way to do that?
- let local_time = datetime.format("%X").unwrap().as_str().to_ascii_lowercase();
-
- if local_time.ends_with("am") || local_time.ends_with("pm") {
- // Use 12h time format (AM/PM)
- datetime.format("%l∶%M %p").unwrap().to_string()
- } else {
- // Use 24 time format
- datetime.format("%R").unwrap().to_string()
- }
- }
-
- /// Find the related event if any
- pub fn related_matrix_event(&self) -> Option<OwnedEventId> {
- match self.imp().event.borrow().as_ref()? {
+ /// The ID of the event this `SupportedEvent` relates to, if any.
+ pub fn related_event_id(&self) -> Option<OwnedEventId> {
+ match self.imp().matrix_event.borrow().as_ref()? {
AnySyncRoomEvent::MessageLike(ref message) => match message {
AnySyncMessageLikeEvent::RoomRedaction(SyncRoomRedactionEvent::Original(event)) => {
Some(event.redacts.clone())
@@ -420,8 +299,6 @@ impl Event {
AnySyncMessageLikeEvent::RoomMessage(SyncMessageLikeEvent::Original(event)) => {
match &event.content.relates_to {
Some(relates_to) => match relates_to {
- // TODO: Figure out Relation::Annotation(), Relation::Reference() but
- // they are pre-specs for now See:
https://github.com/uhoreg/matrix-doc/blob/aggregations-reactions/proposals/2677-reactions.md
Relation::Reply { in_reply_to } => Some(in_reply_to.event_id.clone()),
Relation::Replacement(replacement) => {
Some(replacement.event_id.clone())
@@ -438,63 +315,26 @@ impl Event {
}
}
- /// Whether this event is hidden from the user or displayed in the room
- /// history.
- pub fn is_hidden_event(&self) -> bool {
- let priv_ = self.imp();
-
- if self.related_matrix_event().is_some() {
- if let Some(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
- SyncMessageLikeEvent::Original(message),
- ))) = priv_.event.borrow().as_ref()
- {
- if let Some(Relation::Reply { in_reply_to: _ }) = message.content.relates_to {
- return false;
- }
- }
- return true;
- }
-
- let event = priv_.event.borrow();
-
- // List of all events to be shown.
- match event.as_ref() {
- Some(AnySyncRoomEvent::MessageLike(message)) => !matches!(
- message,
- AnySyncMessageLikeEvent::RoomMessage(SyncMessageLikeEvent::Original(_))
- | AnySyncMessageLikeEvent::RoomEncrypted(SyncMessageLikeEvent::Original(_))
- | AnySyncMessageLikeEvent::Sticker(SyncMessageLikeEvent::Original(_))
- ),
- Some(AnySyncRoomEvent::State(state)) => !matches!(
- state,
- AnySyncStateEvent::RoomCreate(SyncStateEvent::Original(_))
- | AnySyncStateEvent::RoomMember(SyncStateEvent::Original(_))
- | AnySyncStateEvent::RoomThirdPartyInvite(SyncStateEvent::Original(_))
- | AnySyncStateEvent::RoomTombstone(SyncStateEvent::Original(_))
- ),
- _ => true,
- }
- }
-
- /// Whether this is a replacing `Event`.
+ /// Whether this `SupportedEvent` replaces another one.
///
- /// Replacing matrix events are:
+ /// Replacing Matrix events are:
///
/// - `RoomRedaction`
/// - `RoomMessage` with `Relation::Replacement`
pub fn is_replacing_event(&self) -> bool {
- match self.matrix_event() {
- Some(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
+ match self.imp().matrix_event.borrow().as_ref().unwrap() {
+ AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
SyncMessageLikeEvent::Original(message),
- ))) => {
+ )) => {
matches!(message.content.relates_to, Some(Relation::Replacement(_)))
}
- Some(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(_))) => true,
+ AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(_)) => true,
_ => false,
}
}
- pub fn prepend_replacing_events(&self, events: Vec<Event>) {
+ /// Prepend the given events to the list of replacing events.
+ pub fn prepend_replacing_events(&self, events: Vec<SupportedEvent>) {
let priv_ = self.imp();
priv_.replacing_events.borrow_mut().splice(..0, events);
if self.redacted() {
@@ -502,7 +342,8 @@ impl Event {
}
}
- pub fn append_replacing_events(&self, events: Vec<Event>) {
+ /// Append the given events to the list of replacing events.
+ pub fn append_replacing_events(&self, events: Vec<SupportedEvent>) {
let priv_ = self.imp();
let old_replacement = self.replacement();
@@ -536,15 +377,14 @@ impl Event {
}
}
- pub fn replacing_events(&self) -> Vec<Event> {
+ /// The replacing events of this `SupportedEvent`, in the order of the
+ /// timeline.
+ pub fn replacing_events(&self) -> Vec<SupportedEvent> {
self.imp().replacing_events.borrow().clone()
}
- /// The `Event` that replaces this one, if any.
- ///
- /// If this matrix event has been redacted or replaced, returns the
- /// corresponding `Event`, otherwise returns `None`.
- pub fn replacement(&self) -> Option<Event> {
+ /// The event that replaces this `SupportedEvent`, if any.
+ pub fn replacement(&self) -> Option<SupportedEvent> {
self.replacing_events()
.iter()
.rev()
@@ -552,21 +392,19 @@ impl Event {
.cloned()
}
- /// Whether this matrix event has been redacted.
+ /// Whether this `SupportedEvent` has been redacted.
pub fn redacted(&self) -> bool {
self.replacement()
.filter(|event| {
matches!(
event.matrix_event(),
- Some(AnySyncRoomEvent::MessageLike(
- AnySyncMessageLikeEvent::RoomRedaction(_)
- ))
+ AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(_))
)
})
.is_some()
}
- /// Whether this is a reaction.
+ /// Whether this `SupportedEvent` is a reaction.
pub fn is_reaction(&self) -> bool {
matches!(
self.original_content(),
@@ -574,41 +412,38 @@ impl Event {
)
}
- /// The reactions for this event.
+ /// The reactions for this `SupportedEvent`.
pub fn reactions(&self) -> &ReactionList {
&self.imp().reactions
}
- /// Add reactions to this event.
- pub fn add_reactions(&self, reactions: Vec<Event>) {
+ /// Add reactions to this `SupportedEvent`.
+ pub fn add_reactions(&self, reactions: Vec<SupportedEvent>) {
if !self.redacted() {
self.imp().reactions.add_reactions(reactions);
}
}
- /// The content of this matrix event.
- ///
- /// Returns `None` if this is not a message-like event.
+ /// The content of this `SupportedEvent`, if this is a message-like event.
pub fn original_content(&self) -> Option<AnyMessageLikeEventContent> {
- match self.matrix_event()? {
+ match self.matrix_event() {
AnySyncRoomEvent::MessageLike(message) => message.original_content(),
_ => None,
}
}
- /// The content to display for this `Event`.
- ///
- /// If this matrix event has been replaced, returns the replacing `Event`'s
- /// content.
+ /// The content to display for this `SupportedEvent`, if this is a
+ /// message-like event.
///
- /// Returns `None` if this is not a message-like event.
+ /// If this event has been replaced, returns the replacing
+ /// `SupportedEvent`'s content.
pub fn content(&self) -> Option<AnyMessageLikeEventContent> {
self.replacement()
.and_then(|replacement| replacement.content())
.or_else(|| self.original_content())
}
- /// The content of a media message.
+ /// Fetch the content of the media message in this `SupportedEvent`.
///
/// Compatible events:
///
@@ -617,9 +452,11 @@ impl Event {
/// - Video message (`MessageType::Video`).
/// - Audio message (`MessageType::Audio`).
///
- /// Returns `Ok((uid, filename, binary_content))` on success, `Err` if an
- /// error occurred while fetching the content. Panics on an incompatible
- /// event. `uid` is a unique identifier for this media.
+ /// Returns `Ok((uid, filename, binary_content))` on success. `uid` is a
+ /// unique identifier for this media.
+ ///
+ /// Returns `Err` if an error occurred while fetching the content. Panics on
+ /// an incompatible event.
pub async fn get_media_content(&self) -> Result<(String, String, Vec<u8>), matrix_sdk::Error> {
if let AnyMessageLikeEventContent::RoomMessage(content) = self.original_content().unwrap() {
let client = self.room().session().client();
@@ -704,7 +541,7 @@ impl Event {
panic!("Trying to get the media content of an event of incompatible type");
}
- /// Get the id of the event this `Event` replies to, if any.
+ /// Get the ID of the event this `SupportedEvent` replies to, if any.
pub fn reply_to_id(&self) -> Option<OwnedEventId> {
match self.original_content()? {
AnyMessageLikeEventContent::RoomMessage(message) => {
@@ -718,12 +555,12 @@ impl Event {
}
}
- /// Whether this `Event` is a reply to another event.
+ /// Whether this `SupportedEvent` is a reply to another event.
pub fn is_reply(&self) -> bool {
self.reply_to_id().is_some()
}
- /// Get the `Event` this `Event` replies to, if any.
+ /// Get the `Event` this `SupportedEvent` replies to, if any.
///
/// Returns `Ok(None)` if this event is not a reply.
pub async fn reply_to_event(&self) -> Result<Option<Event>, MatrixError> {
@@ -740,4 +577,40 @@ impl Event {
.await?;
Ok(Some(event))
}
+
+ /// Whether this `SupportedEvent` is hidden from the user or displayed in
+ /// the room history.
+ pub fn is_hidden_event(&self) -> bool {
+ let priv_ = self.imp();
+
+ if self.related_event_id().is_some() {
+ if let Some(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
+ SyncMessageLikeEvent::Original(message),
+ ))) = priv_.matrix_event.borrow().as_ref()
+ {
+ if let Some(Relation::Reply { in_reply_to: _ }) = message.content.relates_to {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // List of all events to be shown.
+ match priv_.matrix_event.borrow().as_ref() {
+ Some(AnySyncRoomEvent::MessageLike(message)) => !matches!(
+ message,
+ AnySyncMessageLikeEvent::RoomMessage(SyncMessageLikeEvent::Original(_))
+ | AnySyncMessageLikeEvent::RoomEncrypted(SyncMessageLikeEvent::Original(_))
+ | AnySyncMessageLikeEvent::Sticker(SyncMessageLikeEvent::Original(_))
+ ),
+ Some(AnySyncRoomEvent::State(state)) => !matches!(
+ state,
+ AnySyncStateEvent::RoomCreate(SyncStateEvent::Original(_))
+ | AnySyncStateEvent::RoomMember(SyncStateEvent::Original(_))
+ | AnySyncStateEvent::RoomThirdPartyInvite(SyncStateEvent::Original(_))
+ | AnySyncStateEvent::RoomTombstone(SyncStateEvent::Original(_))
+ ),
+ _ => true,
+ }
+ }
}
diff --git a/src/session/room/event/unsupported_event.rs b/src/session/room/event/unsupported_event.rs
new file mode 100644
index 000000000..1a6a616e3
--- /dev/null
+++ b/src/session/room/event/unsupported_event.rs
@@ -0,0 +1,56 @@
+use gtk::{glib, prelude::*, subclass::prelude::*};
+use matrix_sdk::{deserialized_responses::SyncRoomEvent, ruma::events::RoomEventType};
+
+use super::{BoxedSyncRoomEvent, Event, EventImpl};
+use crate::session::room::{
+ timeline::{TimelineItem, TimelineItemImpl},
+ Room,
+};
+
+mod imp {
+ use super::*;
+
+ #[derive(Debug, Default)]
+ pub struct UnsupportedEvent {}
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for UnsupportedEvent {
+ const NAME: &'static str = "RoomUnsupportedEvent";
+ type Type = super::UnsupportedEvent;
+ type ParentType = Event;
+ }
+
+ impl ObjectImpl for UnsupportedEvent {}
+
+ impl TimelineItemImpl for UnsupportedEvent {}
+
+ impl EventImpl for UnsupportedEvent {}
+}
+
+glib::wrapper! {
+ /// GObject representation of an unsupported Matrix room event.
+ pub struct UnsupportedEvent(ObjectSubclass<imp::UnsupportedEvent>) @extends TimelineItem, Event;
+}
+
+impl UnsupportedEvent {
+ /// Construct an `UnsupportedEvent` from the given pure event and room.
+ pub fn new(pure_event: SyncRoomEvent, room: &Room) -> Self {
+ let pure_event = BoxedSyncRoomEvent(pure_event);
+ glib::Object::new(&[("pure-event", &pure_event), ("room", room)])
+ .expect("Failed to create UnsupportedEvent")
+ }
+
+ /// The type of this `UnsupportedEvent`, if the field is found.
+ pub fn event_type(&self) -> Option<RoomEventType> {
+ self.upcast_ref::<Event>()
+ .imp()
+ .pure_event
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .event
+ .get_field::<RoomEventType>("type")
+ .ok()
+ .flatten()
+ }
+}
diff --git a/src/session/room/event_actions.rs b/src/session/room/event_actions.rs
index 5fc36d51e..7128486db 100644
--- a/src/session/room/event_actions.rs
+++ b/src/session/room/event_actions.rs
@@ -5,10 +5,10 @@ use matrix_sdk::ruma::events::{room::message::MessageType, AnyMessageLikeEventCo
use once_cell::sync::Lazy;
use crate::{
+ prelude::*,
session::{
event_source_dialog::EventSourceDialog,
- room::{Event, RoomAction},
- user::UserExt,
+ room::{Event, RoomAction, SupportedEvent},
},
spawn, toast,
utils::cache_dir,
@@ -90,119 +90,122 @@ where
})
);
- if let Some(AnyMessageLikeEventContent::RoomMessage(message)) = event.content() {
- let user_id = event
- .room()
- .session()
- .user()
- .map(|user| user.user_id())
- .unwrap();
- let user = event.room().members().member_by_id(user_id);
- if event.sender() == user
- || event
+ if let Some(event) = event.downcast_ref::<SupportedEvent>() {
+ if let Some(AnyMessageLikeEventContent::RoomMessage(message)) = event.content() {
+ let user_id = event
.room()
- .power_levels()
- .min_level_for_room_action(&RoomAction::Redact)
- <= user.power_level()
- {
- // Remove message
- gtk_macros::action!(
- &action_group,
- "remove",
- clone!(@weak event, => move |_, _| {
- event.room().redact(event.matrix_event_id(), None);
- })
- );
- }
- // Send/redact a reaction
- gtk_macros::action!(
- &action_group,
- "toggle-reaction",
- Some(&String::static_variant_type()),
- clone!(@weak event => move |_, variant| {
- let key: String = variant.unwrap().get().unwrap();
- let room = event.room();
-
- let reaction_group = event.reactions().reaction_group_by_key(&key);
-
- if let Some(reaction) = reaction_group.and_then(|group| group.user_reaction()) {
- // The user already sent that reaction, redact it.
- room.redact(reaction.matrix_event_id(), None);
- } else {
- // The user didn't send that redaction, send it.
- room.send_reaction(key, event.matrix_event_id());
- }
- })
- );
- match message.msgtype {
- // Copy Text-Message
- MessageType::Text(text_message) => {
+ .session()
+ .user()
+ .map(|user| user.user_id())
+ .unwrap();
+ let user = event.room().members().member_by_id(user_id);
+ if event.sender() == user
+ || event
+ .room()
+ .power_levels()
+ .min_level_for_room_action(&RoomAction::Redact)
+ <= user.power_level()
+ {
+ // Remove message
gtk_macros::action!(
&action_group,
- "copy-text",
- clone!(@weak self as widget => move |_, _| {
- widget.clipboard().set_text(&text_message.body);
+ "remove",
+ clone!(@weak event, => move |_, _| {
+ event.room().redact(event.event_id(), None);
})
);
}
- MessageType::File(_) => {
- // Save message's file
- gtk_macros::action!(
- &action_group,
- "file-save",
- clone!(@weak self as widget, @weak event => move |_, _| {
- widget.save_event_file(event);
- })
- );
+ // Send/redact a reaction
+ gtk_macros::action!(
+ &action_group,
+ "toggle-reaction",
+ Some(&String::static_variant_type()),
+ clone!(@weak event => move |_, variant| {
+ let key: String = variant.unwrap().get().unwrap();
+ let room = event.room();
- // Open message's file
- gtk_macros::action!(
- &action_group,
- "file-open",
- clone!(@weak self as widget, @weak event => move |_, _| {
- widget.open_event_file(event);
- })
- );
- }
- MessageType::Emote(message) => {
- gtk_macros::action!(
- &action_group,
- "copy-text",
- clone!(@weak self as widget, @weak event => move |_, _| {
- let display_name = event.sender().display_name();
- let message = display_name + " " + &message.body;
- widget.clipboard().set_text(&message);
- })
- );
- }
- MessageType::Image(_) => {
- gtk_macros::action!(
- &action_group,
- "save-image",
- clone!(@weak self as widget, @weak event => move |_, _| {
- widget.save_event_file(event);
- })
- );
- }
- MessageType::Video(_) => {
- gtk_macros::action!(
- &action_group,
- "save-video",
- clone!(@weak self as widget, @weak event => move |_, _| {
- widget.save_event_file(event);
- })
- );
- }
- MessageType::Audio(_) => {
- gtk_macros::action!(
- &action_group,
- "save-audio",
- clone!(@weak self as widget, @weak event => move |_, _| {
+ let reaction_group = event.reactions().reaction_group_by_key(&key);
+
+ if let Some(reaction) = reaction_group.and_then(|group| group.user_reaction()) {
+ // The user already sent that reaction, redact it.
+ room.redact(reaction.event_id(), None);
+ } else {
+ // The user didn't send that redaction, send it.
+ room.send_reaction(key, event.event_id());
+ }
+ })
+ );
+ match message.msgtype {
+ // Copy Text-Message
+ MessageType::Text(text_message) => {
+ gtk_macros::action!(
+ &action_group,
+ "copy-text",
+ clone!(@weak self as widget => move |_, _| {
+ widget.clipboard().set_text(&text_message.body);
+ })
+ );
+ }
+ MessageType::File(_) => {
+ // Save message's file
+ gtk_macros::action!(
+ &action_group,
+ "file-save",
+ clone!(@weak self as widget, @weak event => move |_, _| {
widget.save_event_file(event);
- })
- );
+ })
+ );
+
+ // Open message's file
+ gtk_macros::action!(
+ &action_group,
+ "file-open",
+ clone!(@weak self as widget, @weak event => move |_, _| {
+ widget.open_event_file(event);
+ })
+ );
+ }
+ MessageType::Emote(message) => {
+ gtk_macros::action!(
+ &action_group,
+ "copy-text",
+ clone!(@weak self as widget, @weak event => move |_, _| {
+ let display_name = event.sender().display_name();
+ let message = display_name + " " + &message.body;
+ widget.clipboard().set_text(&message);
+ })
+ );
+ }
+
+ MessageType::Image(_) => {
+ gtk_macros::action!(
+ &action_group,
+ "save-image",
+ clone!(@weak self as widget, @weak event => move |_, _| {
+ widget.save_event_file(event);
+ })
+ );
+ }
+ MessageType::Video(_) => {
+ gtk_macros::action!(
+ &action_group,
+ "save-video",
+ clone!(@weak self as widget, @weak event => move |_, _| {
+ widget.save_event_file(event);
+ })
+ );
+ }
+ MessageType::Audio(_) => {
+ gtk_macros::action!(
+ &action_group,
+ "save-audio",
+ clone!(@weak self as widget, @weak event => move |_, _| {
+ widget.save_event_file(event);
+ })
+ );
+ }
+ _ => {}
}
- _ => {}
}
}
self.insert_action_group("event", Some(&action_group));
@@ -211,9 +214,9 @@ where
/// Save the file in `event`.
///
- /// See `Event::get_media_content` for compatible events. Panics on an
- /// incompatible event.
- fn save_event_file(&self, event: Event) {
+ /// See [`SupportedEvent::get_media_content()`] for compatible events.
+ /// Panics on an incompatible event.
+ fn save_event_file(&self, event: SupportedEvent) {
let window: Window = self.root().unwrap().downcast().unwrap();
spawn!(
glib::PRIORITY_LOW,
@@ -260,9 +263,9 @@ where
/// Open the file in `event`.
///
- /// See `Event::get_media_content` for compatible events. Panics on an
- /// incompatible event.
- fn open_event_file(&self, event: Event) {
+ /// See [`SupportedEvent::get_media_content()`] for compatible events.
+ /// Panics on an incompatible event.
+ fn open_event_file(&self, event: SupportedEvent) {
spawn!(
glib::PRIORITY_LOW,
clone!(@weak self as obj => async move {
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index dc9eb0fd3..b1faba8c9 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -46,7 +46,7 @@ use matrix_sdk::{
use ruma::events::SyncEphemeralRoomEvent;
pub use self::{
- event::Event,
+ event::*,
event_actions::EventActions,
highlight_flags::HighlightFlags,
member::{Member, Membership},
@@ -55,10 +55,7 @@ pub use self::{
reaction_group::ReactionGroup,
reaction_list::ReactionList,
room_type::RoomType,
- timeline::{
- Timeline, TimelineDayDivider, TimelineItem, TimelineItemExt, TimelineNewMessagesDivider,
- TimelineSpinner, TimelineState,
- },
+ timeline::*,
};
use super::verification::IdentityVerification;
use crate::{
@@ -103,7 +100,7 @@ mod imp {
/// The event of the user's read receipt for this room.
pub read_receipt: RefCell<Option<Event>>,
/// The latest read event in the room's timeline.
- pub latest_read: RefCell<Option<Event>>,
+ pub latest_read: RefCell<Option<SupportedEvent>>,
/// The highlight state of the room,
pub highlight: Cell<HighlightFlags>,
pub predecessor: OnceCell<OwnedRoomId>,
@@ -827,7 +824,7 @@ impl Room {
if Some(event_id)
== self
.read_receipt()
- .map(|event| event.matrix_event_id())
+ .and_then(|event| event.event_id())
.as_deref()
{
return;
@@ -872,7 +869,7 @@ impl Room {
timeline
.item(i)
.as_ref()
- .and_then(|obj| obj.downcast_ref::<Event>())
+ .and_then(|obj| obj.downcast_ref::<SupportedEvent>())
.and_then(|event| {
// The user sent the event so it's the latest read event.
// Necessary because we don't get read receipts for the user's own events.
@@ -886,9 +883,8 @@ impl Room {
}
// The event is older than the read receipt so it has been read.
- if event.matrix_event().filter(count_as_unread).is_some()
- && event.matrix_origin_server_ts()
- <= read_receipt.matrix_origin_server_ts()
+ if count_as_unread(&event.matrix_event())
+ && event.origin_server_ts() <= read_receipt.origin_server_ts()
{
return Some(event.to_owned());
}
@@ -902,12 +898,12 @@ impl Room {
}
/// The latest read event in the room's timeline.
- pub fn latest_read(&self) -> Option<Event> {
+ pub fn latest_read(&self) -> Option<SupportedEvent> {
self.imp().latest_read.borrow().clone()
}
/// Set the latest read event.
- fn set_latest_read(&self, latest_read: Option<Event>) {
+ fn set_latest_read(&self, latest_read: Option<SupportedEvent>) {
if latest_read == self.latest_read() {
return;
}
@@ -974,7 +970,7 @@ impl Room {
if let Some(event) = timeline
.item(i)
.as_ref()
- .and_then(|obj| obj.downcast_ref::<Event>())
+ .and_then(|obj| obj.downcast_ref::<SupportedEvent>())
{
// This is the event corresponding to the read receipt so there's no unread
// messages.
@@ -983,7 +979,7 @@ impl Room {
}
// The user hasn't read the latest message.
- if event.matrix_event().filter(count_as_unread).is_some() {
+ if count_as_unread(&event.matrix_event()) {
return false;
}
}
@@ -1306,7 +1302,7 @@ impl Room {
};
let raw_event: Raw<AnySyncRoomEvent> = Raw::new(&matrix_event).unwrap().cast();
- let event = Event::new(raw_event.into(), self);
+ let event = SupportedEvent::try_from_event(raw_event.into(), self).unwrap();
self.imp()
.timeline
.get()
@@ -1357,7 +1353,7 @@ impl Room {
if let MatrixRoom::Joined(matrix_room) = self.matrix_room() {
let raw_event: Raw<AnySyncRoomEvent> = Raw::new(&event).unwrap().cast();
- let event = Event::new(raw_event.into(), self);
+ let event = SupportedEvent::try_from_event(raw_event.into(), self).unwrap();
self.imp()
.timeline
.get()
diff --git a/src/session/room/reaction_group.rs b/src/session/room/reaction_group.rs
index 339f7269d..6df0dbd44 100644
--- a/src/session/room/reaction_group.rs
+++ b/src/session/room/reaction_group.rs
@@ -1,7 +1,7 @@
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
-use super::Event;
-use crate::session::UserExt;
+use super::SupportedEvent;
+use crate::prelude::*;
mod imp {
use std::cell::RefCell;
@@ -16,7 +16,7 @@ mod imp {
/// The key of the group.
pub key: OnceCell<String>,
/// The reactions in the group.
- pub reactions: RefCell<IndexSet<Event>>,
+ pub reactions: RefCell<IndexSet<SupportedEvent>>,
}
#[glib::object_subclass]
@@ -108,14 +108,14 @@ impl ReactionGroup {
}
/// The reaction in this group sent by this user, if any.
- pub fn user_reaction(&self) -> Option<Event> {
+ pub fn user_reaction(&self) -> Option<SupportedEvent> {
let reactions = self.imp().reactions.borrow();
if let Some(user) = reactions
.first()
.and_then(|event| event.room().session().user().cloned())
{
for reaction in reactions.iter().filter(|event| !event.redacted()) {
- if reaction.matrix_sender() == user.user_id() {
+ if reaction.sender_id() == user.user_id() {
return Some(reaction.clone());
}
}
@@ -129,7 +129,7 @@ impl ReactionGroup {
}
/// Add new reactions to this group.
- pub fn add_reactions(&self, new_reactions: Vec<Event>) {
+ pub fn add_reactions(&self, new_reactions: Vec<SupportedEvent>) {
let prev_has_user = self.has_user();
let mut added_reactions = Vec::with_capacity(new_reactions.len());
diff --git a/src/session/room/reaction_list.rs b/src/session/room/reaction_list.rs
index 8f711464a..a3d60072e 100644
--- a/src/session/room/reaction_list.rs
+++ b/src/session/room/reaction_list.rs
@@ -3,7 +3,7 @@ use std::collections::HashMap;
use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
use matrix_sdk::ruma::events::AnyMessageLikeEventContent;
-use super::{Event, ReactionGroup};
+use super::{ReactionGroup, SupportedEvent};
mod imp {
use std::cell::RefCell;
@@ -45,7 +45,7 @@ mod imp {
}
glib::wrapper! {
- /// List of all `ReactionGroup`s for an `Event`. Implements `ListModel`.
+ /// List of all `ReactionGroup`s for a `SupportedEvent`. Implements `ListModel`.
///
/// `ReactionGroup`s are sorted in "insertion order".
pub struct ReactionList(ObjectSubclass<imp::ReactionList>)
@@ -57,15 +57,15 @@ impl ReactionList {
glib::Object::new(&[]).expect("Failed to create ReactionList")
}
- /// Add reactions with the given reaction `Event`s.
+ /// Add reactions with the given reaction `SupportedEvent`s.
///
- /// Ignores `Event`s that are not reactions.
- pub fn add_reactions(&self, new_reactions: Vec<Event>) {
+ /// Ignores `SupportedEvent`s that are not reactions.
+ pub fn add_reactions(&self, new_reactions: Vec<SupportedEvent>) {
let mut reactions = self.imp().reactions.borrow_mut();
let prev_len = reactions.len();
// Group reactions by key
- let mut grouped_reactions: HashMap<String, Vec<Event>> = HashMap::new();
+ let mut grouped_reactions: HashMap<String, Vec<SupportedEvent>> = HashMap::new();
for event in new_reactions {
if let Some(AnyMessageLikeEventContent::Reaction(reaction)) = event.content() {
let relation = reaction.relates_to;
diff --git a/src/session/room/timeline/mod.rs b/src/session/room/timeline/mod.rs
index 1e47d51ad..32018a1f0 100644
--- a/src/session/room/timeline/mod.rs
+++ b/src/session/room/timeline/mod.rs
@@ -23,10 +23,8 @@ pub use timeline_new_messages_divider::TimelineNewMessagesDivider;
pub use timeline_spinner::TimelineSpinner;
use tokio::task::JoinHandle;
-use crate::{
- session::room::{Event, Room},
- spawn_tokio,
-};
+use super::{Event, Room, SupportedEvent, UnsupportedEvent};
+use crate::{prelude::*, spawn_tokio};
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
#[repr(u32)]
@@ -195,7 +193,7 @@ impl Timeline {
let mut previous_timestamp = if position > 0 {
list.get(position - 1)
.and_then(|item| item.downcast_ref::<Event>())
- .map(|event| event.timestamp())
+ .and_then(|event| event.timestamp())
} else {
None
};
@@ -204,7 +202,7 @@ impl Timeline {
for current in list.range(position..position + added) {
if let Some(current_timestamp) = current
.downcast_ref::<Event>()
- .map(|event| event.timestamp())
+ .and_then(|event| event.timestamp())
{
if Some(current_timestamp.ymd()) != previous_timestamp.as_ref().map(|t| t.ymd())
{
@@ -252,13 +250,13 @@ impl Timeline {
let mut previous_sender = if position > 0 {
list.get(position - 1)
.filter(|item| item.can_hide_header())
- .and_then(|item| item.sender())
+ .and_then(|item| item.event_sender())
} else {
None
};
for current in list.range(position..position + added) {
- let current_sender = current.sender();
+ let current_sender = current.event_sender();
if !current.can_hide_header() {
current.set_show_header(false);
@@ -281,7 +279,7 @@ impl Timeline {
// Once the sender changes we can be sure that the visibility for headers will
// be correct
- if next.sender() != previous_sender {
+ if next.event_sender() != previous_sender {
next.set_show_header(true);
break;
}
@@ -300,15 +298,16 @@ impl Timeline {
for event in list
.range(position as usize..(position + added) as usize)
- .filter_map(|item| item.downcast_ref::<Event>())
+ .filter_map(|item| item.downcast_ref::<SupportedEvent>())
{
- if let Some(relates_to) = relates_to_events.remove(&event.matrix_event_id()) {
- let mut replacing_events: Vec<Event> = vec![];
- let mut reactions: Vec<Event> = vec![];
+ if let Some(relates_to) = relates_to_events.remove(&event.event_id()) {
+ let mut replacing_events = vec![];
+ let mut reactions = vec![];
for relation_event_id in relates_to {
let relation = self
.event_by_id(&relation_event_id)
+ .and_then(|event| event.downcast::<SupportedEvent>().ok())
.expect("Previously known event has disappeared");
if relation.is_replacing_event() {
@@ -326,7 +325,7 @@ impl Timeline {
event.add_reactions(reactions);
if event.redacted() {
- redacted_events.insert(event.matrix_event_id());
+ redacted_events.insert(event.event_id());
}
}
}
@@ -352,8 +351,8 @@ impl Timeline {
let mut list = list.iter();
while let Some(item) = list.next_back() {
- if let Some(event) = item.downcast_ref::<Event>() {
- if redacted_events.remove(&event.matrix_event_id()) {
+ if let Some(event) = item.downcast_ref::<SupportedEvent>() {
+ if redacted_events.remove(&event.event_id()) {
redacted_events_pos.push(i - 1);
}
if redacted_events.is_empty() {
@@ -412,20 +411,21 @@ impl Timeline {
}
}
- fn add_hidden_events(&self, events: Vec<Event>, at_front: bool) {
+ fn add_hidden_events(&self, events: Vec<SupportedEvent>, at_front: bool) {
let priv_ = self.imp();
let mut relates_to_events = priv_.relates_to_events.borrow_mut();
// Group events by related event
- let mut new_relations: HashMap<OwnedEventId, Vec<Event>> = HashMap::new();
+ let mut new_relations: HashMap<_, Vec<_>> = HashMap::new();
for event in events {
- if let Some(relates_to) = relates_to_events.remove(&event.matrix_event_id()) {
- let mut replacing_events: Vec<Event> = vec![];
- let mut reactions: Vec<Event> = vec![];
+ if let Some(relates_to) = relates_to_events.remove(&event.event_id()) {
+ let mut replacing_events = vec![];
+ let mut reactions = vec![];
for relation_event_id in relates_to {
let relation = self
.event_by_id(&relation_event_id)
+ .and_then(|event| event.downcast::<SupportedEvent>().ok())
.expect("Previously known event has disappeared");
if relation.is_replacing_event() {
@@ -443,7 +443,7 @@ impl Timeline {
event.add_reactions(reactions);
}
- if let Some(relates_to_event) = event.related_matrix_event() {
+ if let Some(relates_to_event) = event.related_event_id() {
let relations = new_relations.entry(relates_to_event).or_default();
relations.push(event);
}
@@ -454,53 +454,54 @@ impl Timeline {
for (relates_to_event_id, new_relations) in new_relations {
if let Some(relates_to_event) = self.event_by_id(&relates_to_event_id) {
// Get the relations in relates_to_event otherwise they will be added in
- // in items_changed and they might not be added at the right place.
- let mut relations: Vec<Event> = relates_to_events
- .remove(&relates_to_event.matrix_event_id())
+ // items_changed and they might not be added at the right place.
+ let mut relations: Vec<_> = relates_to_events
+ .remove(&relates_to_event_id)
.unwrap_or_default()
.into_iter()
.map(|event_id| {
self.event_by_id(&event_id)
+ .and_then(|event| event.downcast::<SupportedEvent>().ok())
.expect("Previously known event has disappeared")
})
.collect();
- if at_front {
- relations.splice(..0, new_relations);
- } else {
- relations.extend(new_relations);
- }
+ if let Some(relates_to_event) = relates_to_event.downcast_ref::<SupportedEvent>() {
+ if at_front {
+ relations.splice(..0, new_relations);
+ } else {
+ relations.extend(new_relations);
+ }
- let mut replacing_events: Vec<Event> = vec![];
- let mut reactions: Vec<Event> = vec![];
+ let mut replacing_events = vec![];
+ let mut reactions = vec![];
- for relation in relations {
- if relation.is_replacing_event() {
- replacing_events.push(relation);
- } else if relation.is_reaction() {
- reactions.push(relation);
+ for relation in relations {
+ if relation.is_replacing_event() {
+ replacing_events.push(relation);
+ } else if relation.is_reaction() {
+ reactions.push(relation);
+ }
}
- }
- if !at_front || relates_to_event.replacing_events().is_empty() {
- relates_to_event.append_replacing_events(replacing_events);
- } else {
- relates_to_event.prepend_replacing_events(replacing_events);
- }
- relates_to_event.add_reactions(reactions);
+ if !at_front || relates_to_event.replacing_events().is_empty() {
+ relates_to_event.append_replacing_events(replacing_events);
+ } else {
+ relates_to_event.prepend_replacing_events(replacing_events);
+ }
+ relates_to_event.add_reactions(reactions);
- if relates_to_event.redacted() {
- redacted_events.insert(relates_to_event.matrix_event_id());
+ if relates_to_event.redacted() {
+ redacted_events.insert(relates_to_event.event_id());
+ }
}
} else {
// Store the new event if the `related_to` event isn't known, we will update the
// `relates_to` once the `related_to` event is added to the list
let relates_to_event = relates_to_events.entry(relates_to_event_id).or_default();
- let relations_ids: Vec<OwnedEventId> = new_relations
- .iter()
- .map(|event| event.matrix_event_id())
- .collect();
+ let relations_ids: Vec<_> =
+ new_relations.iter().map(|event| event.event_id()).collect();
if at_front {
relates_to_event.splice(..0, relations_ids);
} else {
@@ -648,30 +649,43 @@ impl Timeline {
};
let mut pending_events = priv_.pending_events.borrow_mut();
- let mut hidden_events: Vec<Event> = vec![];
-
- for event in batch.into_iter() {
- let event_id = event.matrix_event_id();
+ let mut hidden_events = vec![];
- if let Some(pending_id) = event
- .matrix_transaction_id()
- .and_then(|txn_id| pending_events.remove(&txn_id))
+ for event in batch {
+ if let Some(event_id) = event
+ .downcast_ref::<UnsupportedEvent>()
+ .and_then(|event| event.event_id())
{
- let mut event_map = priv_.event_map.borrow_mut();
-
- if let Some(pending_event) = event_map.remove(&pending_id) {
- pending_event.set_matrix_pure_event(event.matrix_pure_event());
- event_map.insert(event_id, pending_event);
- };
+ priv_.event_map.borrow_mut().insert(event_id, event);
added -= 1;
- } else {
- priv_.event_map.borrow_mut().insert(event_id, event.clone());
- if event.is_hidden_event() {
- hidden_events.push(event);
+ } else if let Ok(event) = event.downcast::<SupportedEvent>() {
+ let event_id = event.event_id();
+
+ if let Some(pending_id) = event
+ .transaction_id()
+ .and_then(|txn_id| pending_events.remove(&txn_id))
+ {
+ let mut event_map = priv_.event_map.borrow_mut();
+
+ if let Some(pending_event) = event_map.remove(&pending_id) {
+ pending_event.set_pure_event(event.pure_event());
+ event_map.insert(event_id, pending_event);
+ };
added -= 1;
} else {
- priv_.list.borrow_mut().push_back(event.upcast());
+ priv_
+ .event_map
+ .borrow_mut()
+ .insert(event_id, event.clone().upcast());
+ if event.is_hidden_event() {
+ hidden_events.push(event);
+ added -= 1;
+ } else {
+ priv_.list.borrow_mut().push_back(event.upcast());
+ }
}
+ } else {
+ added -= 1;
}
}
@@ -684,18 +698,18 @@ impl Timeline {
}
/// Append an event that wasn't yet fully sent and received via a sync
- pub fn append_pending(&self, txn_id: &TransactionId, event: Event) {
+ pub fn append_pending(&self, txn_id: &TransactionId, event: SupportedEvent) {
let priv_ = self.imp();
priv_
.event_map
.borrow_mut()
- .insert(event.matrix_event_id(), event.clone());
+ .insert(event.event_id(), event.clone().upcast());
priv_
.pending_events
.borrow_mut()
- .insert(txn_id.to_owned(), event.matrix_event_id());
+ .insert(txn_id.to_owned(), event.event_id());
let index = {
let mut list = priv_.list.borrow_mut();
@@ -756,22 +770,32 @@ impl Timeline {
let mut added = batch.len();
{
- let mut hidden_events: Vec<Event> = vec![];
+ let mut hidden_events: Vec<_> = vec![];
// Extend the size of the list so that rust doesn't need to reallocate memory
// multiple times
priv_.list.borrow_mut().reserve(added);
for event in batch {
- priv_
- .event_map
- .borrow_mut()
- .insert(event.matrix_event_id(), event.clone());
-
- if event.is_hidden_event() {
- hidden_events.push(event);
+ if let Some(event_id) = event
+ .downcast_ref::<UnsupportedEvent>()
+ .and_then(|event| event.event_id())
+ {
+ priv_.event_map.borrow_mut().insert(event_id, event);
added -= 1;
+ } else if let Ok(event) = event.downcast::<SupportedEvent>() {
+ priv_
+ .event_map
+ .borrow_mut()
+ .insert(event.event_id(), event.clone().upcast());
+
+ if event.is_hidden_event() {
+ hidden_events.push(event);
+ added -= 1;
+ } else {
+ priv_.list.borrow_mut().push_front(event.upcast());
+ }
} else {
- priv_.list.borrow_mut().push_front(event.upcast());
+ added -= 1;
}
}
self.add_hidden_events(hidden_events, true);
diff --git a/src/session/room/timeline/timeline_item.rs b/src/session/room/timeline/timeline_item.rs
index 55ef72656..d4cd32b67 100644
--- a/src/session/room/timeline/timeline_item.rs
+++ b/src/session/room/timeline/timeline_item.rs
@@ -15,7 +15,7 @@ mod imp {
pub selectable: fn(&super::TimelineItem) -> bool,
pub activatable: fn(&super::TimelineItem) -> bool,
pub can_hide_header: fn(&super::TimelineItem) -> bool,
- pub sender: fn(&super::TimelineItem) -> Option<Member>,
+ pub event_sender: fn(&super::TimelineItem) -> Option<Member>,
}
unsafe impl ClassStruct for TimelineItemClass {
@@ -37,9 +37,9 @@ mod imp {
(klass.as_ref().can_hide_header)(this)
}
- pub(super) fn timeline_item_sender(this: &super::TimelineItem) -> Option<Member> {
+ pub(super) fn timeline_item_event_sender(this: &super::TimelineItem) -> Option<Member> {
let klass = this.class();
- (klass.as_ref().sender)(this)
+ (klass.as_ref().event_sender)(this)
}
#[derive(Debug, Default)]
@@ -88,8 +88,8 @@ mod imp {
glib::ParamFlags::READABLE,
),
glib::ParamSpecObject::new(
- "sender",
- "Sender",
+ "event-sender",
+ "Event Sender",
"If this item is a Matrix event, the sender of the event.",
Member::static_type(),
glib::ParamFlags::READABLE,
@@ -119,7 +119,7 @@ mod imp {
"activatable" => obj.activatable().to_value(),
"show-header" => obj.show_header().to_value(),
"can-hide-header" => obj.can_hide_header().to_value(),
- "sender" => obj.sender().to_value(),
+ "event-sender" => obj.event_sender().to_value(),
_ => unimplemented!(),
}
}
@@ -163,7 +163,7 @@ pub trait TimelineItemExt: 'static {
/// If this is a Matrix event, the sender of the event.
///
/// Defaults to `None`.
- fn sender(&self) -> Option<Member>;
+ fn event_sender(&self) -> Option<Member>;
}
impl<O: IsA<TimelineItem>> TimelineItemExt for O {
@@ -194,8 +194,8 @@ impl<O: IsA<TimelineItem>> TimelineItemExt for O {
imp::timeline_item_can_hide_header(self.upcast_ref())
}
- fn sender(&self) -> Option<Member> {
- imp::timeline_item_sender(self.upcast_ref())
+ fn event_sender(&self) -> Option<Member> {
+ imp::timeline_item_event_sender(self.upcast_ref())
}
}
@@ -217,7 +217,7 @@ pub trait TimelineItemImpl: ObjectImpl {
false
}
- fn sender(&self, _obj: &Self::Type) -> Option<Member> {
+ fn event_sender(&self, _obj: &Self::Type) -> Option<Member> {
None
}
}
@@ -236,7 +236,7 @@ where
klass.selectable = selectable_trampoline::<T>;
klass.activatable = activatable_trampoline::<T>;
klass.can_hide_header = can_hide_header_trampoline::<T>;
- klass.sender = sender_trampoline::<T>;
+ klass.event_sender = event_sender_trampoline::<T>;
}
}
@@ -268,11 +268,11 @@ where
this.imp().can_hide_header(this)
}
-fn sender_trampoline<T>(this: &TimelineItem) -> Option<Member>
+fn event_sender_trampoline<T>(this: &TimelineItem) -> Option<Member>
where
T: ObjectSubclass + TimelineItemImpl,
T::Type: IsA<TimelineItem>,
{
let this = this.downcast_ref::<T::Type>().unwrap();
- this.imp().sender(this)
+ this.imp().event_sender(this)
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]