Skip to main content

tuwunel_service/presence/
data.rs

1use 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		// TODO: tighten for state flicker?
91		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
225/// Parses a `UserId` from bytes.
226fn 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}