Skip to main content

tuwunel_service/presence/
mod.rs

1mod aggregate;
2mod data;
3// Write/update pipeline lives in pipeline.rs.
4mod pipeline;
5
6use std::{collections::HashMap, net::IpAddr, sync::Arc, time::Duration};
7
8use async_trait::async_trait;
9use futures::{
10	Stream, StreamExt, TryFutureExt,
11	future::{AbortHandle, Abortable, join},
12	stream::FuturesUnordered,
13};
14use loole::{Receiver, Sender};
15use ruma::{
16	DeviceId, OwnedUserId, UInt, UserId,
17	events::presence::{PresenceEvent, PresenceEventContent},
18	presence::PresenceState,
19};
20use serde::{Deserialize, Serialize};
21use tokio::sync::RwLock;
22use tuwunel_core::{
23	Result, checked, debug, debug_warn, err,
24	result::LogErr,
25	trace,
26	utils::{self, TryFutureExtExt},
27};
28
29use self::{aggregate::PresenceAggregator, data::Data};
30use crate::appservice::RegistrationInfo;
31
32#[derive(Default)]
33pub struct Ping<'a> {
34	pub device_id: Option<&'a DeviceId>,
35	pub client_ip: Option<IpAddr>,
36	pub new_state: Option<&'a PresenceState>,
37	pub appservice: Option<&'a RegistrationInfo>,
38}
39
40/// Represents data required to be kept in order to implement the presence
41/// specification.
42#[derive(Serialize, Deserialize, Debug, Clone)]
43pub(super) struct Presence {
44	pub(super) state: PresenceState,
45	pub(super) currently_active: bool,
46	pub(super) last_active_ts: u64,
47	pub(super) status_msg: Option<String>,
48}
49
50impl Presence {
51	pub(super) fn from_json_bytes(bytes: &[u8]) -> Result<Self> {
52		serde_json::from_slice(bytes)
53			.map_err(|_| err!(Database(error!("Invalid presence data in database"))))
54	}
55}
56
57pub struct Service {
58	timer_channel: (Sender<TimerType>, Receiver<TimerType>),
59	timeout_remote_users: bool,
60	idle_timeout: u64,
61	offline_timeout: u64,
62	db: Data,
63	services: Arc<crate::services::OnceServices>,
64	last_sync_seen: RwLock<HashMap<OwnedUserId, u64>>,
65	device_presence: PresenceAggregator,
66}
67
68type TimerType = (OwnedUserId, Duration, u64);
69type TimerFired = (OwnedUserId, u64);
70
71#[async_trait]
72impl crate::Service for Service {
73	fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
74		let config = &args.server.config;
75		let idle_timeout_s = config.presence_idle_timeout_s;
76		let offline_timeout_s = config.presence_offline_timeout_s;
77		Ok(Arc::new(Self {
78			timer_channel: loole::unbounded(),
79			timeout_remote_users: config.presence_timeout_remote_users,
80			idle_timeout: checked!(idle_timeout_s * 1_000)?,
81			offline_timeout: checked!(offline_timeout_s * 1_000)?,
82			db: Data::new(args),
83			services: args.services.clone(),
84			last_sync_seen: RwLock::new(HashMap::new()),
85			device_presence: PresenceAggregator::new(),
86		}))
87	}
88
89	async fn worker(self: Arc<Self>) -> Result {
90		// reset dormant online/away statuses to offline, and set the server user as
91		// online
92		self.unset_all_presence().await;
93		self.device_presence.clear().await;
94		_ = self
95			.maybe_ping_presence(&self.services.globals.server_user, Ping::default())
96			.await;
97
98		let receiver = self.timer_channel.1.clone();
99
100		let mut presence_timers: FuturesUnordered<_> = FuturesUnordered::new();
101		let mut timer_handles: HashMap<OwnedUserId, (u64, AbortHandle)> = HashMap::new();
102		while !receiver.is_closed() && self.services.server.is_running() {
103			tokio::select! {
104				Some(result) = presence_timers.next() => {
105					let Ok((user_id, count)) = result else {
106						continue;
107					};
108
109					if let Some((current_count, _)) = timer_handles.get(&user_id)
110						&& *current_count != count {
111						trace!(?user_id, count, current_count, "Skipping stale presence timer");
112						continue;
113					}
114
115					timer_handles.remove(&user_id);
116					self.process_presence_timer(&user_id, count).await.log_err().ok();
117				},
118				event = receiver.recv_async() => match event {
119					Ok((user_id, timeout, count)) => {
120						debug!(
121							"Adding timer {}: {user_id} timeout:{timeout:?} count:{count}",
122							presence_timers.len()
123						);
124						if let Some((_, handle)) = timer_handles.remove(&user_id) {
125							handle.abort();
126						}
127
128						let (handle, reg) = AbortHandle::new_pair();
129						presence_timers.push(Abortable::new(
130							pipeline::presence_timer(user_id.clone(), timeout, count),
131							reg,
132						));
133						timer_handles.insert(user_id, (count, handle));
134					},
135					_ => break,
136				},
137			}
138		}
139
140		// set the server user as offline
141		let ping = Ping {
142			new_state: Some(&PresenceState::Offline),
143			..Default::default()
144		};
145
146		_ = self
147			.maybe_ping_presence(&self.services.globals.server_user, ping)
148			.await;
149
150		Ok(())
151	}
152
153	async fn interrupt(&self) {
154		let (timer_sender, _) = &self.timer_channel;
155		if !timer_sender.is_closed() {
156			timer_sender.close();
157		}
158	}
159
160	fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
161}
162
163impl Service {
164	/// record that a user has just successfully completed a /sync (or
165	/// equivalent activity)
166	pub async fn note_sync(&self, user_id: &UserId, appservice: Option<&RegistrationInfo>) {
167		if appservice.is_some() || !self.services.config.suppress_push_when_active {
168			return;
169		}
170
171		let now = utils::millis_since_unix_epoch();
172		self.last_sync_seen
173			.write()
174			.await
175			.insert(user_id.to_owned(), now);
176	}
177
178	/// Returns milliseconds since last observed sync for user (if any)
179	pub async fn last_sync_gap_ms(&self, user_id: &UserId) -> Option<u64> {
180		let now = utils::millis_since_unix_epoch();
181		self.last_sync_seen
182			.read()
183			.await
184			.get(user_id)
185			.map(|ts| now.saturating_sub(*ts))
186	}
187
188	/// Returns the latest presence event for the given user.
189	pub async fn get_presence(&self, user_id: &UserId) -> Result<PresenceEvent> {
190		self.db
191			.get_presence(user_id)
192			.map_ok(|(_, presence)| presence)
193			.await
194	}
195
196	/// Removes the presence record for the given user from the database.
197	///
198	/// TODO: Why is this not used?
199	pub async fn remove_presence(&self, user_id: &UserId) {
200		self.db.remove_presence(user_id).await;
201	}
202
203	// Unset online/unavailable presence to offline on startup
204	async fn unset_all_presence(&self) {
205		if !self.services.server.config.allow_local_presence || self.services.db.is_read_only() {
206			return;
207		}
208
209		let _cork = self.services.db.cork();
210
211		for user_id in &self
212			.services
213			.users
214			.list_local_users()
215			.map(UserId::to_owned)
216			.collect::<Vec<_>>()
217			.await
218		{
219			let presence = self.db.get_presence(user_id).await;
220
221			let presence = match presence {
222				| Ok((_, ref presence)) => &presence.content,
223				| _ => continue,
224			};
225
226			if !matches!(
227				presence.presence,
228				PresenceState::Unavailable | PresenceState::Online | PresenceState::Busy
229			) {
230				trace!(?user_id, ?presence, "Skipping user");
231				continue;
232			}
233
234			trace!(?user_id, ?presence, "Resetting presence to offline");
235
236			_ = self
237				.set_presence(
238					user_id,
239					&PresenceState::Offline,
240					Some(false),
241					presence.last_active_ago,
242					presence.status_msg.clone(),
243				)
244				.await
245				.inspect_err(|e| {
246					debug_warn!(
247						?presence,
248						"{user_id} has invalid presence in database and failed to reset it to \
249						 offline: {e}"
250					);
251				});
252		}
253	}
254
255	/// Returns the most recent presence updates that happened after the event
256	/// with id `since`.
257	pub fn presence_since(
258		&self,
259		since: u64,
260		to: Option<u64>,
261	) -> impl Stream<Item = (&UserId, u64, &[u8])> + Send + '_ {
262		self.db.presence_since(since, to)
263	}
264
265	#[inline]
266	pub async fn from_json_bytes_to_event(
267		&self,
268		bytes: &[u8],
269		user_id: &UserId,
270	) -> Result<PresenceEvent> {
271		let presence = Presence::from_json_bytes(bytes)?;
272		let event = self.to_presence_event(presence, user_id).await;
273
274		Ok(event)
275	}
276
277	/// Creates a PresenceEvent from available data.
278	async fn to_presence_event(&self, presence: Presence, user_id: &UserId) -> PresenceEvent {
279		let now = utils::millis_since_unix_epoch();
280		let last_active_ago = now.saturating_sub(presence.last_active_ts);
281
282		let avatar_url = self.services.profile.avatar_url(user_id).ok();
283		let displayname = self.services.profile.displayname(user_id).ok();
284		let (avatar_url, displayname) = join(avatar_url, displayname).await;
285
286		PresenceEvent {
287			sender: user_id.to_owned(),
288			content: PresenceEventContent {
289				presence: presence.state,
290				status_msg: presence.status_msg,
291				currently_active: Some(presence.currently_active),
292				last_active_ago: Some(UInt::new_saturating(last_active_ago)),
293				avatar_url,
294				displayname,
295			},
296		}
297	}
298}