1use std::{collections::BTreeMap, sync::Arc};
2
3use futures::{
4 Stream, TryStreamExt,
5 future::{join, try_join},
6};
7use ruma::{
8 CanonicalJsonObject, EventId, OwnedEventId, RoomId, UserId,
9 events::{AnySyncEphemeralRoomEvent, receipt::ReceiptEvent},
10 serde::Raw,
11};
12use serde::{Deserialize, de::IgnoredAny};
13use tuwunel_core::{
14 Result, error,
15 matrix::pdu::PduCount,
16 result::NotFound,
17 smallvec::SmallVec,
18 trace,
19 utils::{ReadyExt, TryReadyExt, stream::TryIgnore},
20};
21use tuwunel_database::{Deserialized, Interfix, Json, KeyBuf, Map, Txn, serialize_key};
22
23use super::{PrivateRead, ThreadKind};
24
25pub(super) struct Data {
26 roomuserid_privateread: Arc<Map>,
27 roomuserid_lastprivatereadupdate: Arc<Map>,
28 roomuserid_privatereadsync: Arc<Map>,
29 services: Arc<crate::services::OnceServices>,
30 readreceiptid_readreceipt: Arc<Map>,
31}
32
33pub(super) type ReceiptItem<'a> = (&'a UserId, u64, Raw<AnySyncEphemeralRoomEvent>);
34
35type RowKv<'a> = ((&'a RoomId, &'a UserId, &'a str), (u64, Option<u64>));
37
38type Superseded = SmallVec<[KeyBuf; 1]>;
43
44#[derive(Deserialize)]
50#[expect(clippy::zero_sized_map_values)]
51struct StoredContent {
52 content: BTreeMap<OwnedEventId, IgnoredAny>,
53}
54
55impl Data {
56 pub(super) fn new(args: &crate::Args<'_>) -> Self {
57 let db = &args.db;
58 Self {
59 roomuserid_privateread: db["roomuserid_privateread"].clone(),
60 roomuserid_lastprivatereadupdate: db["roomuserid_lastprivatereadupdate"].clone(),
61 roomuserid_privatereadsync: db["roomuserid_privatereadsync"].clone(),
62 readreceiptid_readreceipt: db["readreceiptid_readreceipt"].clone(),
63 services: args.services.clone(),
64 }
65 }
66
67 #[inline]
74 pub(super) async fn readreceipt_update(
75 &self,
76 user_id: &UserId,
77 room_id: &RoomId,
78 event: &ReceiptEvent,
79 ) -> bool {
80 let Some(event_id) = event.content.keys().next() else {
83 return false;
84 };
85
86 let thread_kind = event_thread_kind(event);
87 let suffix = serialize_key((user_id, thread_kind))
94 .expect("failed to serialize receipt key suffix");
95
96 let user_id_bytes = user_id.as_bytes();
97 let legacy_match = thread_kind.is_empty();
98
99 let room_prefix =
102 serialize_key((room_id, Interfix)).expect("failed to serialize receipt room prefix");
103
104 let last_possible_key = (room_id, u64::MAX);
105 let (superseded, current) = self
106 .readreceiptid_readreceipt
107 .rev_stream_from_raw(&last_possible_key)
108 .ignore_err()
109 .ready_take_while(|(key, _)| key.starts_with(room_prefix.as_slice()))
110 .ready_filter_map(|(key, val)| {
111 (key.ends_with(suffix.as_slice())
112 || (legacy_match && key.ends_with(user_id_bytes)))
113 .then_some((key, val))
114 })
115 .ready_fold((Superseded::new(), None), |(mut superseded, current), (key, val)| {
116 let current = superseded
117 .is_empty()
118 .then_some(val)
119 .and_then(stored_event_id)
120 .or(current);
121
122 superseded.push(key.into());
123
124 (superseded, current)
125 })
126 .await;
127
128 if !self
129 .receipt_advanced(current.as_deref(), event_id)
130 .await
131 {
132 return false;
133 }
134
135 let count = self.services.globals.next_count();
136 let latest_id = (room_id, *count, user_id, thread_kind);
137
138 let mut txn = superseded
139 .iter()
140 .fold(self.services.db.txn(), |mut txn, key| {
141 txn.del_raw(&self.readreceiptid_readreceipt, key);
142 txn
143 });
144
145 txn.put(&self.readreceiptid_readreceipt, latest_id, Json(event));
146 txn.execute();
147
148 true
149 }
150
151 async fn receipt_advanced(&self, current: Option<&EventId>, incoming: &EventId) -> bool {
158 match current {
159 | None => true,
160 | Some(current) if current == incoming => false,
161 | Some(current) => {
162 let (current, incoming) = join(
163 self.services.timeline.get_pdu_count(current),
164 self.services.timeline.get_pdu_count(incoming),
165 )
166 .await;
167
168 position_advances(current.ok(), incoming.ok())
169 },
170 }
171 }
172
173 #[inline]
174 pub(super) fn readreceipts_since<'a>(
175 &'a self,
176 room_id: &'a RoomId,
177 since: u64,
178 to: Option<u64>,
179 ) -> impl Stream<Item = ReceiptItem<'_>> + Send + 'a {
180 self.readreceipts_since_fallible(room_id, since, to)
181 .ignore_err()
182 }
183
184 #[inline]
185 pub(super) fn readreceipts_since_fallible<'a>(
186 &'a self,
187 room_id: &'a RoomId,
188 since: u64,
189 to: Option<u64>,
190 ) -> impl Stream<Item = Result<ReceiptItem<'_>>> + Send + 'a {
191 type Key<'a> = (&'a RoomId, u64, &'a UserId, &'a str);
193 type KeyVal<'a> = (Key<'a>, CanonicalJsonObject);
194
195 let after_since = since.saturating_add(1); let first_possible_edu = (room_id, after_since);
197
198 self.readreceiptid_readreceipt
199 .stream_from(&first_possible_edu)
200 .ready_try_take_while(move |((r, c, ..), _): &KeyVal<'_>| {
201 Ok(*r == room_id && to.is_none_or(|to| *c <= to))
202 })
203 .ready_and_then(move |((_, count, user_id, _), mut json): KeyVal<'_>| {
204 json.remove("room_id");
205
206 let event = serde_json::value::to_raw_value(&json)?;
207
208 Ok((user_id, count, Raw::from_json(event)))
209 })
210 }
211
212 #[inline]
225 pub(super) async fn private_read_set(
226 &self,
227 PrivateRead {
228 room_id,
229 user_id,
230 count,
231 ts,
232 thread,
233 announce,
234 }: PrivateRead<'_>,
235 ) -> bool {
236 let thread_kind = thread.as_str().unwrap_or_default();
237
238 if self
239 .private_read_position(room_id, user_id, thread_kind)
240 .await
241 .is_ok_and(|(stored, _)| count <= stored)
242 {
243 return false;
244 }
245
246 let reset_sync = if announce && !thread_kind.is_empty() {
247 let versions = try_join(
248 self.last_privateread_update_fallible(user_id, room_id),
249 self.private_read_sync_update_fallible(user_id, room_id),
250 )
251 .await;
252
253 match versions {
254 | Ok((gate, snapshot)) => gate != snapshot,
255 | Err(error) => {
256 error!(?error, "Failed to inspect the private read sync snapshot.");
257 return false;
258 },
259 }
260 } else {
261 false
262 };
263
264 let mut txn = self
265 .sweep_thread_private_reads(
266 &self.roomuserid_privateread,
267 room_id,
268 user_id,
269 thread_kind,
270 self.services.db.txn(),
271 )
272 .await;
273
274 if announce && (thread_kind.is_empty() || reset_sync) {
275 txn = match self
276 .sweep_private_read_sync(room_id, user_id, txn)
277 .await
278 {
279 | Ok(txn) => txn,
280 | Err(error) => {
281 error!(?error, "Failed to reset the private read sync snapshot.");
282 return false;
283 },
284 };
285 }
286
287 let next_count = announce.then(|| self.services.globals.next_count());
289 let ts = u64::from(ts.get());
290
291 if let Some(next_count) = next_count.as_deref() {
292 txn.put(&self.roomuserid_lastprivatereadupdate, (room_id, user_id), *next_count);
293 txn.put(&self.roomuserid_privatereadsync, (room_id, user_id), *next_count);
294 txn.put(
295 &self.roomuserid_privatereadsync,
296 (room_id, user_id, thread_kind),
297 (count, ts),
298 );
299 }
300
301 match thread_kind.is_empty() {
303 | true => txn.put(&self.roomuserid_privateread, (room_id, user_id), (count, ts)),
304 | false => txn.put(
305 &self.roomuserid_privateread,
306 (room_id, user_id, thread_kind),
307 (count, ts),
308 ),
309 }
310
311 txn.execute();
312
313 true
314 }
315
316 #[inline]
321 async fn private_read_position(
322 &self,
323 room_id: &RoomId,
324 user_id: &UserId,
325 thread_kind: &str,
326 ) -> Result<(u64, Option<u64>)> {
327 match thread_kind.is_empty() {
328 | true =>
329 self.private_read_get_count(room_id, user_id)
330 .await,
331 | false => self
332 .roomuserid_privateread
333 .qry(&(room_id, user_id, thread_kind))
334 .await
335 .deserialized(),
336 }
337 }
338
339 #[inline]
342 pub(super) async fn private_read_get_count(
343 &self,
344 room_id: &RoomId,
345 user_id: &UserId,
346 ) -> Result<(u64, Option<u64>)> {
347 let key = (room_id, user_id);
348 self.roomuserid_privateread
349 .qry(&key)
350 .await
351 .deserialized()
352 }
353
354 #[inline]
355 pub(super) async fn private_read_sync_get_count(
356 &self,
357 room_id: &RoomId,
358 user_id: &UserId,
359 ) -> Result<(u64, Option<u64>)> {
360 let key = (room_id, user_id, "");
361 self.roomuserid_privatereadsync
362 .qry(&key)
363 .await
364 .deserialized()
365 }
366
367 #[inline]
368 pub(super) fn private_read_threaded_stream<'a>(
369 &'a self,
370 room_id: &'a RoomId,
371 user_id: &'a UserId,
372 ) -> impl Stream<Item = (ThreadKind, u64, Option<u64>)> + Send + 'a {
373 private_read_row_stream(&self.roomuserid_privateread, room_id, user_id).ignore_err()
374 }
375
376 #[inline]
381 async fn sweep_thread_private_reads(
382 &self,
383 map: &Arc<Map>,
384 room_id: &RoomId,
385 user_id: &UserId,
386 thread_kind: &str,
387 txn: Txn,
388 ) -> Txn {
389 if !thread_kind.is_empty() {
390 return txn;
391 }
392
393 let prefix = (room_id, user_id, Interfix);
394
395 map.keys_prefix_raw(&prefix)
396 .ignore_err()
397 .ready_fold(txn, |mut txn, key| {
398 txn.del_raw(map, key);
399 txn
400 })
401 .await
402 }
403
404 #[inline]
405 async fn sweep_private_read_sync(
406 &self,
407 room_id: &RoomId,
408 user_id: &UserId,
409 txn: Txn,
410 ) -> Result<Txn> {
411 let prefix = (room_id, user_id, Interfix);
412
413 self.roomuserid_privatereadsync
414 .keys_prefix_raw(&prefix)
415 .ready_try_fold(txn, |mut txn, key| {
416 txn.del_raw(&self.roomuserid_privatereadsync, key);
417 Ok(txn)
418 })
419 .await
420 }
421
422 #[inline]
423 pub(super) fn private_read_sync_stream_fallible<'a>(
424 &'a self,
425 room_id: &'a RoomId,
426 user_id: &'a UserId,
427 ) -> impl Stream<Item = Result<(ThreadKind, u64, Option<u64>)>> + Send + 'a {
428 private_read_row_stream(&self.roomuserid_privatereadsync, room_id, user_id)
429 }
430
431 #[inline]
432 pub(super) async fn private_read_sync_update_fallible(
433 &self,
434 user_id: &UserId,
435 room_id: &RoomId,
436 ) -> Result<u64> {
437 let key = (room_id, user_id);
438 self.roomuserid_privatereadsync
439 .qry(&key)
440 .await
441 .deserialized()
442 .optional()
443 .map(|count| count.unwrap_or(0))
444 }
445
446 #[inline]
447 pub(super) async fn last_privateread_update(
448 &self,
449 user_id: &UserId,
450 room_id: &RoomId,
451 ) -> u64 {
452 self.last_privateread_update_fallible(user_id, room_id)
453 .await
454 .unwrap_or_default()
455 }
456
457 #[inline]
458 pub(super) async fn last_privateread_update_fallible(
459 &self,
460 user_id: &UserId,
461 room_id: &RoomId,
462 ) -> Result<u64> {
463 let key = (room_id, user_id);
464 self.roomuserid_lastprivatereadupdate
465 .qry(&key)
466 .await
467 .deserialized()
468 .optional()
469 .map(|count| count.unwrap_or(0))
470 }
471
472 #[inline]
473 pub(super) async fn delete_all_read_receipts(&self, room_id: &RoomId) -> Result {
474 let prefix = (room_id, Interfix);
475
476 self.roomuserid_privateread
477 .keys_prefix_raw(&prefix)
478 .ignore_err()
479 .ready_for_each(|key| {
480 trace!("Removing key: {key:?}");
481 self.roomuserid_privateread.remove(key);
482 })
483 .await;
484
485 self.roomuserid_lastprivatereadupdate
486 .keys_prefix_raw(&prefix)
487 .ignore_err()
488 .ready_for_each(|key| {
489 trace!("Removing key: {key:?}");
490 self.roomuserid_lastprivatereadupdate.remove(key);
491 })
492 .await;
493
494 self.roomuserid_privatereadsync
495 .keys_prefix_raw(&prefix)
496 .ignore_err()
497 .ready_for_each(|key| {
498 trace!("Removing key: {key:?}");
499 self.roomuserid_privatereadsync.remove(key);
500 })
501 .await;
502
503 self.readreceiptid_readreceipt
504 .keys_prefix_raw(&prefix)
505 .ignore_err()
506 .ready_for_each(|key| {
507 trace!("Removing key: {key:?}");
508 self.readreceiptid_readreceipt.remove(key);
509 })
510 .await;
511
512 Ok(())
513 }
514}
515
516fn private_read_row_stream<'a>(
521 map: &'a Arc<Map>,
522 room_id: &'a RoomId,
523 user_id: &'a UserId,
524) -> impl Stream<Item = Result<(ThreadKind, u64, Option<u64>)>> + Send + 'a {
525 let prefix = (room_id, user_id, Interfix);
526
527 map.stream_prefix(&prefix)
528 .map_ok(|((_, _, kind), (count, ts)): RowKv<'_>| (ThreadKind::from(kind), count, ts))
529}
530
531fn event_thread_kind(event: &ReceiptEvent) -> &str {
545 debug_assert!(
546 event
547 .content
548 .values()
549 .all(|by_type| by_type.len() == 1
550 && by_type.values().all(|by_user| by_user.len() == 1))
551 && event.content.len() == 1,
552 "receipt event must carry exactly one (event_id, type, user) triple"
553 );
554
555 event
556 .content
557 .values()
558 .next()
559 .and_then(|by_type| by_type.values().next())
560 .and_then(|by_user| by_user.values().next())
561 .and_then(|receipt| receipt.thread.as_str())
562 .unwrap_or_default()
563}
564
565fn stored_event_id(val: &[u8]) -> Option<OwnedEventId> {
570 serde_json::from_slice::<StoredContent>(val)
571 .ok()?
572 .content
573 .into_keys()
574 .next()
575}
576
577pub(super) fn position_advances(current: Option<PduCount>, incoming: Option<PduCount>) -> bool {
582 current
583 .zip(incoming)
584 .is_none_or(|(current, incoming)| incoming > current)
585}