tuwunel_service/presence/
mod.rs1mod aggregate;
2mod data;
3mod 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#[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 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 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 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 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 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 pub async fn remove_presence(&self, user_id: &UserId) {
200 self.db.remove_presence(user_id).await;
201 }
202
203 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 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 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}