fractal/session/view/content/room_details/history_viewer/
event.rs

1use gtk::{glib, prelude::*, subclass::prelude::*};
2use matrix_sdk::deserialized_responses::TimelineEvent;
3use ruma::{
4    events::{
5        room::message::{MessageType, OriginalSyncRoomMessageEvent, Relation},
6        AnySyncMessageLikeEvent, AnySyncTimelineEvent, SyncMessageLikeEvent,
7    },
8    OwnedEventId,
9};
10
11use crate::{
12    session::model::Room,
13    utils::matrix::{MediaMessage, VisualMediaMessage},
14};
15
16/// The types of events that can be displayed in the history viewers.
17#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, glib::Enum)]
18#[enum_type(name = "HistoryViewerEventType")]
19pub enum HistoryViewerEventType {
20    /// A file.
21    #[default]
22    File,
23    /// An image or a video.
24    Media,
25    /// An audio file.
26    Audio,
27}
28
29impl HistoryViewerEventType {
30    fn with_msgtype(msgtype: &MessageType) -> Option<Self> {
31        let event_type = match msgtype {
32            MessageType::Audio(_) => Self::Audio,
33            MessageType::File(_) => Self::File,
34            MessageType::Image(_) | MessageType::Video(_) => Self::Media,
35            _ => return None,
36        };
37
38        Some(event_type)
39    }
40}
41
42mod imp {
43    use std::cell::{Cell, OnceCell};
44
45    use super::*;
46
47    #[derive(Debug, Default, glib::Properties)]
48    #[properties(wrapper_type = super::HistoryViewerEvent)]
49    pub struct HistoryViewerEvent {
50        /// The room containing this event.
51        #[property(get, construct_only)]
52        room: glib::WeakRef<Room>,
53        /// The Matrix event.
54        matrix_event: OnceCell<OriginalSyncRoomMessageEvent>,
55        /// The type of the event.
56        #[property(get, construct_only, builder(HistoryViewerEventType::default()))]
57        event_type: Cell<HistoryViewerEventType>,
58    }
59
60    #[glib::object_subclass]
61    impl ObjectSubclass for HistoryViewerEvent {
62        const NAME: &'static str = "HistoryViewerEvent";
63        type Type = super::HistoryViewerEvent;
64    }
65
66    #[glib::derived_properties]
67    impl ObjectImpl for HistoryViewerEvent {}
68
69    impl HistoryViewerEvent {
70        /// Set the Matrix event.
71        pub(super) fn set_matrix_event(&self, event: OriginalSyncRoomMessageEvent) {
72            self.matrix_event
73                .set(event)
74                .expect("Matrix event should be uninitialized");
75        }
76
77        /// The Matrix event.
78        pub(super) fn matrix_event(&self) -> &OriginalSyncRoomMessageEvent {
79            self.matrix_event
80                .get()
81                .expect("Matrix event should be initialized")
82        }
83    }
84}
85
86glib::wrapper! {
87    /// An event in the history viewer's timeline.
88    pub struct HistoryViewerEvent(ObjectSubclass<imp::HistoryViewerEvent>);
89}
90
91impl HistoryViewerEvent {
92    /// Constructs a new `HistoryViewerEvent` with the given event, if it is
93    /// viewable in one of the history viewers.
94    pub fn try_new(room: &Room, event: &TimelineEvent) -> Option<Self> {
95        let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
96            SyncMessageLikeEvent::Original(mut message_event),
97        ))) = event.raw().deserialize()
98        else {
99            return None;
100        };
101
102        // Filter out edits, they should be bundled with the original event.
103        if matches!(
104            message_event.content.relates_to,
105            Some(Relation::Replacement(_))
106        ) {
107            return None;
108        }
109
110        // Apply bundled edit.
111        if let Some(Relation::Replacement(replacement)) = message_event
112            .unsigned
113            .relations
114            .replace
115            .as_ref()
116            .and_then(|e| e.content.relates_to.as_ref())
117        {
118            message_event
119                .content
120                .apply_replacement(replacement.new_content.clone());
121        }
122
123        let event_type = HistoryViewerEventType::with_msgtype(&message_event.content.msgtype)?;
124
125        let obj = glib::Object::builder::<Self>()
126            .property("room", room)
127            .property("event-type", event_type)
128            .build();
129        obj.imp().set_matrix_event(message_event);
130
131        Some(obj)
132    }
133
134    /// The Matrix event.
135    pub(crate) fn matrix_event(&self) -> &OriginalSyncRoomMessageEvent {
136        self.imp().matrix_event()
137    }
138
139    /// The event ID of the inner event.
140    pub(crate) fn event_id(&self) -> OwnedEventId {
141        self.matrix_event().event_id.clone()
142    }
143
144    /// The media message content of this event.
145    pub(crate) fn media_message(&self) -> MediaMessage {
146        MediaMessage::from_message(&self.matrix_event().content.msgtype)
147            .expect("HistoryViewerEvents are all media messages")
148    }
149
150    /// The visual media message of this event, if any.
151    pub(crate) fn visual_media_message(&self) -> Option<VisualMediaMessage> {
152        VisualMediaMessage::from_message(&self.matrix_event().content.msgtype)
153    }
154
155    /// Get the binary content of this event.
156    pub(crate) async fn get_file_content(&self) -> Result<Vec<u8>, matrix_sdk::Error> {
157        let Some(room) = self.room() else {
158            return Err(matrix_sdk::Error::UnknownError(
159                "Could not upgrade Room".into(),
160            ));
161        };
162        let Some(session) = room.session() else {
163            return Err(matrix_sdk::Error::UnknownError(
164                "Could not upgrade Session".into(),
165            ));
166        };
167
168        let client = session.client();
169        self.media_message().into_content(&client).await
170    }
171}