Skip to main content

tuwunel_service/pusher/
badge.rs

1//! Last-delivered push badge counts.
2//!
3//! Remembers, per user and pushkey, the unread total the push gateway last
4//! accepted. Intentionally in-memory only: an absent entry forces the next
5//! counts-only refresh to send, so a restart reconciles every pusher with a
6//! badge this server has never observed. Reaping on pusher delete,
7//! replacement, and device removal bounds the map near the live pusher
8//! population.
9
10use std::{
11	collections::{BTreeMap, HashMap},
12	sync::{Mutex, MutexGuard, PoisonError},
13};
14
15use ruma::{OwnedUserId, UInt, UserId};
16use tuwunel_core::implement;
17
18type Badges = HashMap<OwnedUserId, BTreeMap<String, UInt>>;
19
20#[derive(Default)]
21pub(super) struct SentBadges {
22	inner: Mutex<Badges>,
23}
24
25/// Return the unread total last accepted by this pusher's gateway.
26///
27/// `None` means no delivery has been confirmed since startup, and the caller
28/// must send rather than assume agreement.
29#[implement(super::Service)]
30pub(super) fn sent_badge(&self, user_id: &UserId, pushkey: &str) -> Option<UInt> {
31	self.sent_badges
32		.lock()
33		.get(user_id)
34		.and_then(|pushkeys| pushkeys.get(pushkey))
35		.copied()
36}
37
38impl SentBadges {
39	fn lock(&self) -> MutexGuard<'_, Badges> {
40		self.inner
41			.lock()
42			.unwrap_or_else(PoisonError::into_inner)
43	}
44}
45
46/// Record the unread total a pusher's gateway just accepted.
47///
48/// Call only after a successful delivery; recording an attempt would make a
49/// failed send look reconciled and suppress the retry's refresh.
50#[implement(super::Service)]
51pub(super) fn record_sent_badge(&self, user_id: &UserId, pushkey: &str, unread: UInt) {
52	self.sent_badges
53		.lock()
54		.entry(user_id.to_owned())
55		.or_default()
56		.insert(pushkey.to_owned(), unread);
57}
58
59/// Forget the delivery record for one pusher.
60///
61/// A deleted or replaced pusher leaves the device state unknown, so the next
62/// refresh must send unconditionally.
63#[implement(super::Service)]
64pub(super) fn forget_sent_badge(&self, user_id: &UserId, pushkey: &str) {
65	let mut badges = self.sent_badges.lock();
66	let Some(pushkeys) = badges.get_mut(user_id) else {
67		return;
68	};
69
70	pushkeys.remove(pushkey);
71
72	if pushkeys.is_empty() {
73		badges.remove(user_id);
74	}
75}