diff --git a/fractal-gtk/src/model/message.rs b/fractal-gtk/src/model/message.rs index 1bfa753b..b06e6468 100644 --- a/fractal-gtk/src/model/message.rs +++ b/fractal-gtk/src/model/message.rs @@ -433,6 +433,15 @@ impl Message { } } + /// Returns all event IDs this message relates to. + pub fn relations(&self) -> Vec { + vec![self.in_reply_to.as_ref(), self.replace.as_ref()] + .into_iter() + .flat_map(|r| r.into_iter()) + .cloned() + .collect() + } + /// Generates an unique transaction id for this message /// The txn_id is generated using the md5sum of a concatenation of the message room id, the /// message body and the date. diff --git a/fractal-gtk/src/model/message_list.rs b/fractal-gtk/src/model/message_list.rs index ac476449..7f944324 100644 --- a/fractal-gtk/src/model/message_list.rs +++ b/fractal-gtk/src/model/message_list.rs @@ -1,5 +1,7 @@ use crate::model::message::Message; use matrix_sdk::identifiers::EventId; +use std::collections::{HashMap, HashSet}; +use std::iter; use std::iter::FromIterator; use std::slice::Iter; @@ -7,6 +9,7 @@ use std::slice::Iter; #[derive(Debug, Default, Clone)] pub struct MessageList { messages: Vec, + relating_messages: HashMap>, } impl MessageList { @@ -34,6 +37,11 @@ impl MessageList { /// Inserts the message at the correct position replacing its older version. pub fn add(&mut self, msg: Message) { assert!(msg.id.is_some()); + let id = msg.id.clone().unwrap(); + + if msg.redacted { + self.remove_relations(&id); + } // Deduplication only happens for messages with the same date, so we have // to manually go through the message list and remove possible duplicates. @@ -45,6 +53,10 @@ impl MessageList { // brute-force-fix this by searching all messages for duplicates. self.messages.retain(|m| m.id != msg.id); + if !msg.redacted { + self.populate_relations(&msg); + } + match self.messages.binary_search(&msg) { Ok(idx) => self.messages[idx] = msg, Err(idx) => self.messages.insert(idx, msg), @@ -52,6 +64,59 @@ impl MessageList { // TODO: Use is_sorted (https://github.com/rust-lang/rust/issues/53485) // debug_assert!(self.messages.is_sorted()); } + + /// Updates records of those relations the message is involved in. + /// + /// This updates both, relating and related, messages. + fn populate_relations(&mut self, msg: &Message) { + // Other messages relate to `msg` + let id = msg.id.as_ref().cloned().unwrap(); + let relating = self.find_and_get_relating(&id); + self.relating_messages.insert(id.clone(), relating); + + // `msg` relates to other messages + if let Some(replace_id) = &msg.replace { + self.update_relating(replace_id, iter::once(&id).cloned().collect()); + } + } + + /// Remove all outgoing relations for the given event. + fn remove_relations(&mut self, event_id: &EventId) { + let msg = unwrap_or_unit_return!(self.get(event_id)); + let relations = msg.relations(); + + let event_sets = self.relating_messages.iter_mut().filter_map(|(id, rs)| { + if relations.contains(&id) { + Some(rs) + } else { + None + } + }); + + for set in event_sets { + set.retain(|id| id != event_id); + } + } + + /// Records new messages relating to the message with the given id. + /// + /// This does not remove other messages relating to the given id. + fn update_relating(&mut self, id: &EventId, relating: HashSet) { + let new_relating = match self.relating_messages.remove(id) { + Some(old_relating) => old_relating.union(&relating).cloned().collect(), + None => relating, + }; + self.relating_messages.insert(id.clone(), new_relating); + } + + /// Finds and returns all messages relating to the given one. + fn find_and_get_relating(&self, id: &EventId) -> HashSet { + self.messages + .iter() + .filter(|m| m.replace.as_ref() == Some(id) || m.in_reply_to.as_ref() == Some(id)) + .map(|m| m.id.clone().unwrap()) + .collect() + } } impl FromIterator for MessageList {