tuwunel_service/presence/
data.rs1use std::sync::Arc;
2
3use futures::Stream;
4use ruma::{UInt, UserId, events::presence::PresenceEvent, presence::PresenceState};
5use tuwunel_core::{
6 Result, debug_warn, implement, utils,
7 utils::{ReadyExt, result::NotFound, stream::TryIgnore},
8};
9use tuwunel_database::{Deserialized, Json, Map};
10
11use crate::presence::Presence;
12
13pub(crate) struct Data {
14 presenceid_presence: Arc<Map>,
15 userid_presenceid: Arc<Map>,
16 services: Arc<crate::services::OnceServices>,
17}
18
19impl Data {
20 pub(super) fn new(args: &crate::Args<'_>) -> Self {
21 let db = &args.db;
22 Self {
23 presenceid_presence: db["presenceid_presence"].clone(),
24 userid_presenceid: db["userid_presenceid"].clone(),
25 services: args.services.clone(),
26 }
27 }
28
29 pub(super) async fn get_presence_raw(&self, user_id: &UserId) -> Result<(u64, Presence)> {
30 let count = self
31 .userid_presenceid
32 .get(user_id)
33 .await
34 .deserialized::<u64>()?;
35
36 let key = presenceid_key(count, user_id);
37 let bytes = self.presenceid_presence.get(&key).await?;
38 let presence = Presence::from_json_bytes(&bytes)?;
39
40 Ok((count, presence))
41 }
42
43 pub(super) async fn set_presence(
44 &self,
45 user_id: &UserId,
46 presence_state: &PresenceState,
47 currently_active: Option<bool>,
48 last_active_ago: Option<UInt>,
49 status_msg: Option<String>,
50 ) -> Result<Option<u64>> {
51 let last_presence = self.get_presence(user_id).await;
52 let state_changed = match last_presence {
53 | Err(_) => true,
54 | Ok(ref presence) => presence.1.content.presence != *presence_state,
55 };
56
57 let status_msg_changed = match last_presence {
58 | Err(_) => true,
59 | Ok(ref last_presence) => {
60 let old_msg = last_presence
61 .1
62 .content
63 .status_msg
64 .clone()
65 .unwrap_or_default();
66
67 let new_msg = status_msg.clone().unwrap_or_default();
68
69 new_msg != old_msg
70 },
71 };
72
73 let now = utils::millis_since_unix_epoch();
74 let last_last_active_ts = match last_presence {
75 | Err(_) => 0,
76 | Ok((_, ref presence)) => now.saturating_sub(
77 presence
78 .content
79 .last_active_ago
80 .unwrap_or_default()
81 .into(),
82 ),
83 };
84
85 let last_active_ts = match last_active_ago {
86 | None => now,
87 | Some(last_active_ago) => now.saturating_sub(last_active_ago.into()),
88 };
89
90 if !status_msg_changed && !state_changed && last_active_ts < last_last_active_ts {
92 debug_warn!(
93 "presence spam {user_id:?} last_active_ts:{last_active_ts:?} < \
94 {last_last_active_ts:?}",
95 );
96 return Ok(None);
97 }
98
99 let status_msg = if status_msg.as_ref().is_some_and(String::is_empty) {
100 None
101 } else {
102 status_msg
103 };
104
105 let presence = Presence {
106 state: presence_state.to_owned(),
107 currently_active: currently_active.unwrap_or(false),
108 last_active_ts,
109 status_msg,
110 };
111
112 let count = self.services.globals.next_count();
113 let key = presenceid_key(*count, user_id);
114
115 self.userid_presenceid.raw_put(user_id, *count);
116 self.presenceid_presence
117 .raw_put(key, Json(presence));
118
119 if let Ok((last_count, _)) = last_presence {
120 let key = presenceid_key(last_count, user_id);
121 self.presenceid_presence.remove(&key);
122 }
123
124 Ok(Some(*count))
125 }
126
127 #[inline]
128 pub(super) async fn remove_presence(&self, user_id: &UserId) {
129 let Ok(count) = self
130 .userid_presenceid
131 .get(user_id)
132 .await
133 .deserialized::<u64>()
134 else {
135 return;
136 };
137
138 let key = presenceid_key(count, user_id);
139 self.presenceid_presence.remove(&key);
140 self.userid_presenceid.remove(user_id);
141 }
142
143 #[inline]
144 pub(super) fn presence_since(
145 &self,
146 since: u64,
147 to: Option<u64>,
148 ) -> impl Stream<Item = (&UserId, u64, &[u8])> + Send + '_ {
149 self.presenceid_presence
150 .raw_stream()
151 .ignore_err()
152 .ready_filter_map(move |(key, presence)| {
153 let (count, user_id) = presenceid_parse(key).ok()?;
154 (count > since && to.is_none_or(|to| count <= to))
155 .then_some((user_id, count, presence))
156 })
157 }
158}
159
160#[implement(Data)]
161#[inline]
162pub(super) async fn get_presence(&self, user_id: &UserId) -> Result<(u64, PresenceEvent)> {
163 let count = self
164 .userid_presenceid
165 .get(user_id)
166 .await
167 .deserialized::<u64>()?;
168
169 let event = self.get_presence_event(count, user_id).await?;
170
171 Ok((count, event))
172}
173
174#[implement(Data)]
175#[inline]
176pub(super) async fn get_presence_optional(
177 &self,
178 user_id: &UserId,
179) -> Result<Option<(u64, PresenceEvent)>> {
180 let Some(count) = self
181 .userid_presenceid
182 .get(user_id)
183 .await
184 .optional()?
185 else {
186 return Ok(None);
187 };
188
189 let count = count.deserialized::<u64>()?;
190 let event = self.get_presence_event(count, user_id).await?;
191
192 Ok(Some((count, event)))
193}
194
195#[implement(Data)]
196#[tracing::instrument(level = "trace", skip(self))]
197async fn get_presence_event(&self, count: u64, user_id: &UserId) -> Result<PresenceEvent> {
198 let key = presenceid_key(count, user_id);
199 let bytes = self.presenceid_presence.get(&key).await?;
200
201 self.services
202 .presence
203 .from_json_bytes_to_event(&bytes, user_id)
204 .await
205}
206
207#[inline]
208fn presenceid_key(count: u64, user_id: &UserId) -> Vec<u8> {
209 let cap = size_of::<u64>().saturating_add(user_id.as_bytes().len());
210 let mut key = Vec::with_capacity(cap);
211 key.extend_from_slice(&count.to_be_bytes());
212 key.extend_from_slice(user_id.as_bytes());
213 key
214}
215
216#[inline]
217fn presenceid_parse(key: &[u8]) -> Result<(u64, &UserId)> {
218 let (count, user_id) = key.split_at(8);
219 let user_id = user_id_from_bytes(user_id)?;
220 let count = utils::u64_from_u8(count);
221
222 Ok((count, user_id))
223}
224
225fn user_id_from_bytes(bytes: &[u8]) -> Result<&UserId> {
227 let str: &str = utils::str_from_bytes(bytes)?;
228 let user_id: &UserId = str.try_into()?;
229
230 Ok(user_id)
231}