1use std::collections::{HashMap, HashSet};
16
17use matrix_sdk_base::{
18 deserialized_responses::TimelineEventKind,
19 event_cache::{Event, Gap, store::EventCacheStoreLockGuard},
20 executor::spawn,
21 linked_chunk::{ChunkMetadata, LinkedChunkId, OwnedLinkedChunkId, Update},
22};
23use ruma::{EventId, RoomId, events::relation::RelationType, serde::Raw};
24use tokio::sync::broadcast::Sender;
25use tracing::trace;
26
27use super::{
28 EventCacheError, Result,
29 caches::{
30 EventLocation, event_linked_chunk::EventLinkedChunk, room::RoomEventCacheLinkedChunkUpdate,
31 },
32};
33
34pub(super) async fn load_linked_chunk_metadata(
40 store_guard: &EventCacheStoreLockGuard,
41 linked_chunk_id: LinkedChunkId<'_>,
42) -> Result<Option<Vec<ChunkMetadata>>> {
43 let mut all_chunks = store_guard
44 .load_all_chunks_metadata(linked_chunk_id)
45 .await
46 .map_err(EventCacheError::from)?;
47
48 if all_chunks.is_empty() {
49 return Ok(None);
51 }
52
53 let chunk_map: HashMap<_, _> = all_chunks.iter().map(|meta| (meta.identifier, meta)).collect();
55
56 let mut iter = all_chunks.iter().filter(|meta| meta.next.is_none());
58 let Some(last) = iter.next() else {
59 return Err(EventCacheError::InvalidLinkedChunkMetadata {
60 details: "no last chunk found".to_owned(),
61 });
62 };
63
64 if let Some(other_last) = iter.next() {
66 return Err(EventCacheError::InvalidLinkedChunkMetadata {
67 details: format!(
68 "chunks {} and {} both claim to be last chunks",
69 last.identifier.index(),
70 other_last.identifier.index()
71 ),
72 });
73 }
74
75 let mut seen = HashSet::new();
78 let mut current = last;
79 loop {
80 if !seen.insert(current.identifier) {
82 return Err(EventCacheError::InvalidLinkedChunkMetadata {
83 details: format!(
84 "cycle detected in linked chunk at {}",
85 current.identifier.index()
86 ),
87 });
88 }
89
90 let Some(prev_id) = current.previous else {
91 if seen.len() != all_chunks.len() {
93 return Err(EventCacheError::InvalidLinkedChunkMetadata {
94 details: format!(
95 "linked chunk likely has multiple components: {} chunks seen through the chain of predecessors, but {} expected",
96 seen.len(),
97 all_chunks.len()
98 ),
99 });
100 }
101 break;
102 };
103
104 let Some(pred_meta) = chunk_map.get(&prev_id) else {
107 return Err(EventCacheError::InvalidLinkedChunkMetadata {
108 details: format!(
109 "missing predecessor {} chunk for {}",
110 prev_id.index(),
111 current.identifier.index()
112 ),
113 });
114 };
115
116 if pred_meta.next != Some(current.identifier) {
118 return Err(EventCacheError::InvalidLinkedChunkMetadata {
119 details: format!(
120 "chunk {}'s next ({:?}) doesn't match the current chunk ({})",
121 pred_meta.identifier.index(),
122 pred_meta.next.map(|chunk_id| chunk_id.index()),
123 current.identifier.index()
124 ),
125 });
126 }
127
128 current = *pred_meta;
129 }
130
131 let mut current = current.identifier;
139
140 for i in 0..all_chunks.len() {
141 let j = all_chunks
143 .iter()
144 .rev()
145 .position(|meta| meta.identifier == current)
146 .map(|j| all_chunks.len() - 1 - j)
147 .expect("the target chunk must be present in the metadata");
148
149 if i != j {
150 all_chunks.swap(i, j);
151 }
152
153 if let Some(next) = all_chunks[i].next {
154 current = next;
155 }
156 }
157
158 Ok(Some(all_chunks))
159}
160
161pub(super) async fn send_updates_to_store(
164 store: &EventCacheStoreLockGuard,
165 linked_chunk_id: OwnedLinkedChunkId,
166 linked_chunk_update_sender: &Sender<RoomEventCacheLinkedChunkUpdate>,
167 mut updates: Vec<Update<Event, Gap>>,
168) -> Result<()> {
169 if updates.is_empty() {
170 return Ok(());
171 }
172
173 for update in updates.iter_mut() {
186 match update {
187 Update::PushItems { items, .. } => strip_relations_from_events(items),
188 Update::ReplaceItem { item, .. } => strip_relations_from_event(item),
189 Update::NewItemsChunk { .. }
191 | Update::NewGapChunk { .. }
192 | Update::RemoveChunk(_)
193 | Update::RemoveItem { .. }
194 | Update::DetachLastItems { .. }
195 | Update::StartReattachItems
196 | Update::EndReattachItems
197 | Update::Clear => {}
198 }
199 }
200
201 let store = store.clone();
208 let cloned_updates = updates.clone();
209 let cloned_linked_chunk_id = linked_chunk_id.clone();
210
211 spawn(async move {
212 trace!(updates = ?cloned_updates, "sending linked chunk updates to the store");
213
214 store.handle_linked_chunk_updates(cloned_linked_chunk_id.as_ref(), cloned_updates).await?;
215 trace!("linked chunk updates applied");
216
217 Result::Ok(())
218 })
219 .await
220 .expect("joining failed")?;
221
222 let _ = linked_chunk_update_sender
224 .send(RoomEventCacheLinkedChunkUpdate { linked_chunk_id, updates });
225
226 Ok(())
227}
228
229fn strip_relations_from_events(items: &mut [Event]) {
231 for ev in items.iter_mut() {
232 strip_relations_from_event(ev);
233 }
234}
235
236fn strip_relations_from_event(ev: &mut Event) {
238 match &mut ev.kind {
239 TimelineEventKind::Decrypted(decrypted) => {
240 decrypted.unsigned_encryption_info = None;
243
244 strip_relations_if_present(&mut decrypted.event);
246 }
247
248 TimelineEventKind::UnableToDecrypt { event, .. }
249 | TimelineEventKind::PlainText { event } => {
250 strip_relations_if_present(event);
251 }
252 }
253}
254
255fn strip_relations_if_present<T>(event: &mut Raw<T>) {
259 let mut closure = || -> Option<()> {
263 let mut val: serde_json::Value = event.deserialize_as().ok()?;
264 let unsigned = val.get_mut("unsigned")?;
265 let unsigned_obj = unsigned.as_object_mut()?;
266 if unsigned_obj.remove("m.relations").is_some() {
267 *event = Raw::new(&val).ok()?.cast_unchecked();
268 }
269 None
270 };
271 let _ = closure();
272}
273
274pub async fn find_event(
276 event_id: &EventId,
277 room_id: &RoomId,
278 event_linked_chunk: &EventLinkedChunk,
279 store: &EventCacheStoreLockGuard,
280) -> Result<Option<(EventLocation, Event)>> {
281 for (position, event) in event_linked_chunk.revents() {
284 if event.event_id() == Some(event_id) {
285 return Ok(Some((EventLocation::Memory(position), event.clone())));
286 }
287 }
288
289 Ok(store.find_event(room_id, event_id).await?.map(|event| (EventLocation::Store, event)))
290}
291
292pub async fn find_event_with_relations(
305 event_id: &EventId,
306 room_id: &RoomId,
307 filters: Option<Vec<RelationType>>,
308 event_linked_chunk: &EventLinkedChunk,
309 store: &EventCacheStoreLockGuard,
310) -> Result<Option<(Event, Vec<Event>)>> {
311 let found = store.find_event(room_id, event_id).await?;
313
314 let Some(target) = found else {
315 return Ok(None);
317 };
318
319 let related =
321 find_event_relations(event_id, room_id, filters, event_linked_chunk, store).await?;
322
323 Ok(Some((target, related)))
324}
325
326pub async fn find_event_relations(
339 event_id: &EventId,
340 room_id: &RoomId,
341 filters: Option<Vec<RelationType>>,
342 event_linked_chunk: &EventLinkedChunk,
343 store: &EventCacheStoreLockGuard,
344) -> Result<Vec<Event>> {
345 let mut related = store.find_event_relations(room_id, event_id, filters.as_deref()).await?;
348 let mut stack = related
349 .iter()
350 .filter_map(|(event, _pos)| event.event_id().map(ToOwned::to_owned))
351 .collect::<Vec<_>>();
352
353 let mut already_seen = HashSet::new();
356 already_seen.insert(event_id.to_owned());
357
358 let mut num_iters = 1;
359
360 while let Some(event_id) = stack.pop() {
362 if !already_seen.insert(event_id.clone()) {
363 continue;
365 }
366
367 let other_related =
368 store.find_event_relations(room_id, &event_id, filters.as_deref()).await?;
369
370 stack.extend(
371 other_related
372 .iter()
373 .filter_map(|(event, _pos)| event.event_id().map(ToOwned::to_owned)),
374 );
375 related.extend(other_related);
376
377 num_iters += 1;
378 }
379
380 trace!(num_related = %related.len(), num_iters, "computed transitive closure of related events");
381
382 related.sort_by(|(_, lhs), (_, rhs)| {
386 use std::cmp::Ordering;
387
388 match (lhs, rhs) {
389 (None, None) => Ordering::Equal,
390 (None, Some(_)) => Ordering::Less,
391 (Some(_), None) => Ordering::Greater,
392 (Some(lhs), Some(rhs)) => {
393 let lhs = event_linked_chunk.event_order(*lhs);
394 let rhs = event_linked_chunk.event_order(*rhs);
395
396 match (lhs, rhs) {
400 (None, None) => Ordering::Equal,
401 (None, Some(_)) => Ordering::Less,
402 (Some(_), None) => Ordering::Greater,
403 (Some(lhs), Some(rhs)) => lhs.cmp(&rhs),
404 }
405 }
406 }
407 });
408
409 let related = related.into_iter().map(|(event, _pos)| event).collect();
411
412 Ok(related)
413}