1mod data;
2#[cfg(test)]
3mod tests;
4
5use std::{collections::BTreeMap, sync::Arc};
6
7use futures::{Stream, StreamExt, TryStreamExt};
8use ruma::{
9 MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, RoomId, UInt, UserId,
10 api::appservice::event::push_events::v1::EphemeralData,
11 events::{
12 AnySyncEphemeralRoomEvent, SyncEphemeralRoomEvent,
13 receipt::{
14 Receipt, ReceiptEvent, ReceiptEventContent, ReceiptThread, ReceiptType, Receipts,
15 },
16 },
17 serde::Raw,
18};
19use serde_json::value::to_raw_value;
20use tuwunel_core::{
21 Result, debug,
22 debug::INFO_SPAN_LEVEL,
23 err,
24 matrix::{
25 Event,
26 pdu::{PduCount, PduId, RawPduId},
27 },
28 smallstr::SmallString,
29 smallvec::SmallVec,
30 utils::{BoolExt, IterStream},
31 warn,
32};
33
34use self::data::{Data, ReceiptItem};
35
36pub type PrivateReadEvents = SmallVec<[Raw<AnySyncEphemeralRoomEvent>; 1]>;
40
41type ThreadKind = SmallString<[u8; 48]>;
46
47#[derive(Clone, Copy, Debug)]
54pub struct PrivateRead<'a> {
55 pub room_id: &'a RoomId,
56 pub user_id: &'a UserId,
57 pub count: u64,
58 pub ts: MilliSecondsSinceUnixEpoch,
59 pub thread: &'a ReceiptThread,
60 pub announce: bool,
61}
62
63pub struct Service {
64 services: Arc<crate::services::OnceServices>,
65 db: Data,
66}
67
68impl crate::Service for Service {
69 fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
70 Ok(Arc::new(Self {
71 services: args.services.clone(),
72 db: Data::new(args),
73 }))
74 }
75
76 fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
77}
78
79impl Service {
80 #[tracing::instrument(
85 name = "receipt"
86 level = INFO_SPAN_LEVEL,
87 skip_all,
88 fields(
89 %room_id,
90 %user_id,
91 ?event.content
92 )
93 )]
94 pub async fn readreceipt_update(
95 &self,
96 user_id: &UserId,
97 room_id: &RoomId,
98 event: &ReceiptEvent,
99 ) -> bool {
100 if self
101 .db
102 .readreceipt_update(user_id, room_id, event)
103 .await
104 .is_false()
105 {
106 return false;
107 }
108
109 self.services
110 .sending
111 .send_edu_room_appservices(room_id, |buf| {
112 let edu = EphemeralData::Receipt(ReceiptEvent {
113 content: event.content.clone(),
114 room_id: room_id.to_owned(),
115 });
116
117 Ok(serde_json::to_writer(buf, &edu)?)
118 })
119 .await
120 .expect("edu serialization or flush failed");
121
122 if self.services.globals.user_is_local(user_id) {
123 self.services
124 .sending
125 .flush_room(room_id)
126 .await
127 .expect("room flush failed");
128 }
129
130 true
131 }
132
133 #[tracing::instrument(skip(self), level = "debug", name = "get_private")]
137 pub async fn private_read_get(
138 &self,
139 room_id: &RoomId,
140 user_id: &UserId,
141 ) -> Result<PrivateReadEvents> {
142 let shortroomid = self
143 .services
144 .short
145 .get_shortroomid(room_id)
146 .await
147 .map_err(|e| {
148 err!(Database(warn!(
149 "Short room ID does not exist in database for {room_id}: {e}"
150 )))
151 })?;
152
153 let legacy = self
154 .private_read_get_count(room_id, user_id)
155 .await
156 .ok()
157 .map(|(count, ts)| (ThreadKind::new(), count, ts));
158
159 let events = legacy
160 .into_iter()
161 .stream()
162 .chain(
163 self.db
164 .private_read_threaded_stream(room_id, user_id),
165 )
166 .filter_map(async |(kind, count, ts)| {
167 self.build_private_read_event(shortroomid, count, ts, user_id, &kind)
168 .await
169 })
170 .collect()
171 .await;
172
173 Ok(events)
174 }
175
176 #[tracing::instrument(skip(self), level = "debug", name = "get_private_fallible")]
187 pub async fn private_read_get_fallible(
188 &self,
189 room_id: &RoomId,
190 user_id: &UserId,
191 update: u64,
192 ) -> Result<PrivateReadEvents> {
193 let snapshot = self
194 .db
195 .private_read_sync_update_fallible(user_id, room_id)
196 .await?;
197
198 if snapshot < update {
199 debug!(%room_id, %user_id, "Serving pre-mirror private read from the active store.");
200 return self.private_read_get(room_id, user_id).await;
201 }
202
203 if snapshot > update {
204 return Err(err!(Database(
205 "Private read snapshot advanced while assembling a bounded sync range."
206 )));
207 }
208
209 let shortroomid = async {
210 self.services
211 .short
212 .get_shortroomid(room_id)
213 .await
214 .map_err(|e| {
215 err!(Database(warn!(
216 "Short room ID does not exist in database for {room_id}: {e}"
217 )))
218 })
219 };
220
221 let shortroomid = shortroomid.await?;
222 let events = self
223 .db
224 .private_read_sync_stream_fallible(room_id, user_id)
225 .try_filter_map(async |(kind, count, ts)| {
226 self.build_private_read_event_skippable(shortroomid, count, ts, user_id, &kind)
227 .await
228 })
229 .try_collect()
230 .await?;
231
232 let confirmed = self
233 .db
234 .private_read_sync_update_fallible(user_id, room_id)
235 .await?;
236
237 if confirmed != update {
238 return Err(err!(Database(
239 "Private read snapshot changed while assembling a bounded sync range."
240 )));
241 }
242
243 Ok(events)
244 }
245
246 async fn build_private_read_event_skippable(
253 &self,
254 shortroomid: u64,
255 count: u64,
256 ts: Option<u64>,
257 user_id: &UserId,
258 thread_kind: &str,
259 ) -> Result<Option<Raw<AnySyncEphemeralRoomEvent>>> {
260 let skip = || {
261 debug!(
262 count,
263 thread_kind,
264 %user_id,
265 "Skipping a private read marker naming a missing event."
266 );
267
268 None
269 };
270
271 self.build_private_read_event_fallible(shortroomid, count, ts, user_id, thread_kind)
272 .await
273 .map(Some)
274 .or_else(|error| error.is_not_found().then(skip).ok_or(error))
275 }
276
277 async fn build_private_read_event(
278 &self,
279 shortroomid: u64,
280 count: u64,
281 ts: Option<u64>,
282 user_id: &UserId,
283 thread_kind: &str,
284 ) -> Option<Raw<AnySyncEphemeralRoomEvent>> {
285 let thread = thread_kind_to_receipt(thread_kind).unwrap_or(ReceiptThread::Unthreaded);
286 let ts = ts
287 .and_then(UInt::new)
288 .map(MilliSecondsSinceUnixEpoch);
289
290 self.build_private_read_event_from(shortroomid, count, ts, user_id, thread)
291 .await
292 .ok()
293 }
294
295 async fn build_private_read_event_fallible(
296 &self,
297 shortroomid: u64,
298 count: u64,
299 ts: Option<u64>,
300 user_id: &UserId,
301 thread_kind: &str,
302 ) -> Result<Raw<AnySyncEphemeralRoomEvent>> {
303 let thread = thread_kind_to_receipt(thread_kind)?;
304 let ts = ts
305 .map(|ts| {
306 UInt::new(ts)
307 .map(MilliSecondsSinceUnixEpoch)
308 .ok_or_else(|| err!(Database("Invalid private receipt timestamp {ts}.")))
309 })
310 .transpose()?;
311
312 self.build_private_read_event_from(shortroomid, count, ts, user_id, thread)
313 .await
314 }
315
316 async fn build_private_read_event_from(
317 &self,
318 shortroomid: u64,
319 count: u64,
320 ts: Option<MilliSecondsSinceUnixEpoch>,
321 user_id: &UserId,
322 thread: ReceiptThread,
323 ) -> Result<Raw<AnySyncEphemeralRoomEvent>> {
324 let pdu_id: RawPduId = PduId {
325 shortroomid,
326 count: PduCount::Normal(count),
327 }
328 .into();
329 let pdu = self
330 .services
331 .timeline
332 .get_pdu_from_id(&pdu_id)
333 .await?;
334
335 let event_id: OwnedEventId = pdu.event_id().to_owned();
336 let user_id: OwnedUserId = user_id.to_owned();
337 let content: BTreeMap<OwnedEventId, Receipts> = BTreeMap::from_iter([(
338 event_id,
339 BTreeMap::from_iter([(
340 ReceiptType::ReadPrivate,
341 BTreeMap::from_iter([(user_id, Receipt { ts, thread })]),
342 )]),
343 )]);
344
345 let receipt_event_content = ReceiptEventContent(content);
346 let receipt_sync_event = SyncEphemeralRoomEvent { content: receipt_event_content };
347 let event = to_raw_value(&receipt_sync_event)?;
348
349 Ok(Raw::from_json(event))
350 }
351
352 #[tracing::instrument(skip(self), level = "debug")]
355 pub fn readreceipts_since<'a>(
356 &'a self,
357 room_id: &'a RoomId,
358 since: u64,
359 to: Option<u64>,
360 ) -> impl Stream<Item = ReceiptItem<'_>> + Send + 'a {
361 self.db.readreceipts_since(room_id, since, to)
362 }
363
364 #[tracing::instrument(skip(self), level = "debug")]
371 pub fn readreceipts_since_fallible<'a>(
372 &'a self,
373 room_id: &'a RoomId,
374 since: u64,
375 to: Option<u64>,
376 ) -> impl Stream<Item = Result<ReceiptItem<'_>>> + Send + 'a {
377 self.db
378 .readreceipts_since_fallible(room_id, since, to)
379 }
380
381 #[tracing::instrument(skip(self), level = "debug", name = "set_private")]
387 pub async fn private_read_set(&self, private_read: PrivateRead<'_>) -> bool {
388 self.db.private_read_set(private_read).await
389 }
390
391 #[tracing::instrument(
393 name = "get_private_count",
394 level = "debug",
395 skip(self),
396 ret(level = "trace")
397 )]
398 pub async fn private_read_get_count(
399 &self,
400 room_id: &RoomId,
401 user_id: &UserId,
402 ) -> Result<(u64, Option<u64>)> {
403 self.db
404 .private_read_get_count(room_id, user_id)
405 .await
406 }
407
408 #[tracing::instrument(
410 name = "get_private_sync_count",
411 level = "debug",
412 skip(self),
413 ret(level = "trace")
414 )]
415 pub async fn private_read_sync_get_count(
416 &self,
417 room_id: &RoomId,
418 user_id: &UserId,
419 ) -> Result<(u64, Option<u64>)> {
420 self.db
421 .private_read_sync_get_count(room_id, user_id)
422 .await
423 }
424
425 #[tracing::instrument(
431 name = "get_private_last",
432 level = "debug",
433 skip(self),
434 ret(level = "trace")
435 )]
436 pub async fn last_privateread_update(&self, user_id: &UserId, room_id: &RoomId) -> u64 {
437 self.db
438 .last_privateread_update(user_id, room_id)
439 .await
440 }
441
442 #[tracing::instrument(
448 name = "get_private_last_fallible",
449 level = "debug",
450 skip(self),
451 ret(level = "trace")
452 )]
453 pub async fn last_privateread_update_fallible(
454 &self,
455 user_id: &UserId,
456 room_id: &RoomId,
457 ) -> Result<u64> {
458 self.db
459 .last_privateread_update_fallible(user_id, room_id)
460 .await
461 }
462
463 pub async fn delete_all_read_receipts(&self, room_id: &RoomId) -> Result {
464 self.db.delete_all_read_receipts(room_id).await
465 }
466}
467
468fn thread_kind_to_receipt(thread_kind: &str) -> Result<ReceiptThread> {
472 match thread_kind {
473 | "" => Ok(ReceiptThread::Unthreaded),
474 | "main" => Ok(ReceiptThread::Main),
475 | _ => OwnedEventId::try_from(thread_kind)
476 .map(ReceiptThread::Thread)
477 .map_err(|error| err!(Database("Invalid private receipt thread: {error}"))),
478 }
479}
480
481pub fn pack_receipts_fallible<I>(
487 mut receipts: I,
488) -> Result<Raw<SyncEphemeralRoomEvent<ReceiptEventContent>>>
489where
490 I: Iterator<Item = Raw<AnySyncEphemeralRoomEvent>>,
491{
492 let json = receipts.try_fold(BTreeMap::new(), |mut json, value| -> Result<_> {
493 let value = serde_json::from_str::<SyncEphemeralRoomEvent<ReceiptEventContent>>(
494 value.json().get(),
495 )?;
496
497 for (event, receipt) in value.content {
498 json.insert(event, receipt);
499 }
500
501 Ok(json)
502 })?;
503
504 let content = ReceiptEventContent(json);
505 let event = to_raw_value(&SyncEphemeralRoomEvent { content })?;
506
507 Ok(Raw::from_json(event))
508}