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