tuwunel_api_client/sync/v3/joined/
notifications.rs1use std::collections::BTreeMap;
2
3use futures::FutureExt;
4use ruma::{
5 OwnedEventId, RoomId, UInt, UserId, api::client::sync::sync_events::UnreadNotificationsCount,
6 uint,
7};
8use tuwunel_core::utils::{BoolExt, TryFutureExtExt};
9use tuwunel_service::Services;
10
11use super::ThreadCounts;
12
13pub(in super::super) struct GateInputs<'a> {
14 pub(in super::super) last_notification_read: Option<u64>,
15 pub(in super::super) thread_last_reads: &'a BTreeMap<OwnedEventId, u64>,
16 pub(in super::super) quiet_round: bool,
17 pub(in super::super) since: u64,
18}
19
20pub(in super::super) struct Gates<F> {
21 pub(in super::super) send_notification_counts: bool,
22 pub(in super::super) send_notification_count_filter: F,
23}
24
25pub(in super::super) fn count_futures<'a>(
26 services: &'a Services,
27 sender_user: &'a UserId,
28 room_id: &'a RoomId,
29 send: bool,
30) -> (
31 impl Future<Output = Option<UInt>> + Send + 'a,
32 impl Future<Output = Option<UInt>> + Send + 'a,
33 impl Future<Output = Option<ThreadCounts>> + Send + 'a,
34) {
35 let notification_count = send.then_async(move || {
36 services
37 .pusher
38 .notification_count(sender_user, room_id)
39 .map(TryInto::try_into)
40 .unwrap_or(uint!(0))
41 });
42
43 let highlight_count = send.then_async(move || {
44 services
45 .pusher
46 .highlight_count(sender_user, room_id)
47 .map(TryInto::try_into)
48 .unwrap_or(uint!(0))
49 });
50
51 let thread_counts = send.then_async(move || {
53 services
54 .pusher
55 .thread_notification_counts(sender_user, room_id)
56 });
57
58 (notification_count, highlight_count, thread_counts)
59}
60
61pub(in super::super) fn in_window(since: u64, next_batch: u64) -> impl Fn(u64) -> bool + Copy {
67 move |count| count > since && count <= next_batch
68}
69
70pub(in super::super) fn compute_gates<F: Fn(u64) -> bool>(
78 GateInputs {
79 last_notification_read,
80 thread_last_reads,
81 quiet_round,
82 since,
83 }: GateInputs<'_>,
84 in_window: F,
85) -> Gates<impl Fn(&UInt) -> bool + use<F>> {
86 let send_notification_counts = !quiet_round
89 || last_notification_read.is_none_or(&in_window)
90 || thread_last_reads
91 .values()
92 .copied()
93 .any(&in_window);
94
95 let after_since = |count: u64| count > since;
96
97 let send_notification_resets = last_notification_read.is_some_and(after_since)
100 || thread_last_reads
101 .values()
102 .copied()
103 .any(&after_since);
104
105 let send_notification_count_filter =
106 move |count: &UInt| *count != uint!(0) || send_notification_resets;
107
108 Gates {
109 send_notification_counts,
110 send_notification_count_filter,
111 }
112}
113
114pub(in super::super) fn assemble_unread(
115 notification_count: Option<UInt>,
116 highlight_count: Option<UInt>,
117 thread_counts: Option<&ThreadCounts>,
118 send_notification_count_filter: impl Fn(&UInt) -> bool,
119 want_thread_unread: bool,
120) -> UnreadNotificationsCount {
121 let (thread_total_notifications, thread_total_highlights) = thread_counts
124 .filter(|_| want_thread_unread.is_false())
125 .into_iter()
126 .flat_map(BTreeMap::values)
127 .fold((0_u64, 0_u64), |(n, h), &(notifs, hl)| {
128 (n.saturating_add(notifs), h.saturating_add(hl))
129 });
130
131 let merge_total = |total: u64| {
132 move |count: UInt| count.saturating_add(UInt::try_from(total).unwrap_or_default())
133 };
134
135 UnreadNotificationsCount {
136 highlight_count: highlight_count
137 .map(merge_total(thread_total_highlights))
138 .filter(&send_notification_count_filter),
139 notification_count: notification_count
140 .map(merge_total(thread_total_notifications))
141 .filter(&send_notification_count_filter),
142 }
143}
144
145pub(in super::super) fn send_thread_counts<'a, I>(
151 reported: bool,
152 thread_roots: I,
153 admitted: impl Fn(&OwnedEventId) -> bool,
154) -> bool
155where
156 I: IntoIterator<Item = &'a OwnedEventId>,
157{
158 reported || thread_roots.into_iter().any(admitted)
159}
160
161pub(in super::super) fn assemble_threads(
167 thread_counts: Option<ThreadCounts>,
168 send_zero: impl Fn(&OwnedEventId) -> bool,
169) -> BTreeMap<OwnedEventId, UnreadNotificationsCount> {
170 thread_counts
171 .into_iter()
172 .flatten()
173 .filter(|(root, counts)| *counts != (0, 0) || send_zero(root))
174 .map(|(root, (notifications, highlights))| {
175 let counts = UnreadNotificationsCount {
176 notification_count: UInt::try_from(notifications).ok(),
177 highlight_count: UInt::try_from(highlights).ok(),
178 };
179
180 (root, counts)
181 })
182 .collect()
183}