2026-01-18 16:31:36 +11:00
|
|
|
mod aggregate;
|
2022-06-25 16:12:23 +02:00
|
|
|
mod data;
|
2024-07-27 23:25:13 +00:00
|
|
|
mod presence;
|
2023-07-10 16:41:00 +02:00
|
|
|
|
2025-09-02 14:26:12 +02:00
|
|
|
use std::{collections::HashMap, sync::Arc, time::Duration};
|
2022-09-06 23:15:09 +02:00
|
|
|
|
2024-07-04 03:26:19 +00:00
|
|
|
use async_trait::async_trait;
|
2026-01-18 16:31:36 +11:00
|
|
|
use futures::{
|
|
|
|
|
Stream, StreamExt, TryFutureExt,
|
|
|
|
|
future::{AbortHandle, Abortable, try_join},
|
|
|
|
|
stream::FuturesUnordered,
|
|
|
|
|
};
|
2024-12-22 22:58:37 +00:00
|
|
|
use loole::{Receiver, Sender};
|
2025-11-01 21:37:13 +00:00
|
|
|
use ruma::{
|
|
|
|
|
DeviceId, OwnedUserId, UInt, UserId, events::presence::PresenceEvent, presence::PresenceState,
|
|
|
|
|
};
|
2025-09-02 17:16:41 +02:00
|
|
|
use tokio::{sync::RwLock, time::sleep};
|
2025-11-01 21:37:13 +00:00
|
|
|
use tuwunel_core::{
|
2026-01-17 05:38:09 +05:00
|
|
|
Error, Result, checked, debug, debug_warn, error,
|
|
|
|
|
result::LogErr,
|
|
|
|
|
trace,
|
2026-01-21 05:00:53 +11:00
|
|
|
utils::future::OptionFutureExt,
|
2025-11-01 21:37:13 +00:00
|
|
|
};
|
2023-07-10 16:41:00 +02:00
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
use self::{aggregate::PresenceAggregator, data::Data, presence::Presence};
|
2023-07-10 16:41:00 +02:00
|
|
|
|
2024-05-09 15:59:08 -07:00
|
|
|
pub struct Service {
|
2024-12-22 22:58:37 +00:00
|
|
|
timer_channel: (Sender<TimerType>, Receiver<TimerType>),
|
2024-04-01 20:48:40 -07:00
|
|
|
timeout_remote_users: bool,
|
2024-07-07 04:46:16 +00:00
|
|
|
idle_timeout: u64,
|
|
|
|
|
offline_timeout: u64,
|
2025-01-10 23:51:08 -05:00
|
|
|
db: Data,
|
2025-08-22 20:15:54 +05:00
|
|
|
services: Arc<crate::services::OnceServices>,
|
2025-09-02 14:26:12 +02:00
|
|
|
last_sync_seen: RwLock<HashMap<OwnedUserId, u64>>,
|
2026-01-18 16:31:36 +11:00
|
|
|
device_presence: PresenceAggregator,
|
2024-07-18 06:37:47 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
type TimerType = (OwnedUserId, Duration, u64);
|
|
|
|
|
type TimerFired = (OwnedUserId, u64);
|
2024-07-27 23:25:13 +00:00
|
|
|
|
2024-07-04 03:26:19 +00:00
|
|
|
#[async_trait]
|
|
|
|
|
impl crate::Service for Service {
|
2025-09-17 14:21:55 +05:00
|
|
|
fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
|
2024-07-04 03:26:19 +00:00
|
|
|
let config = &args.server.config;
|
2024-07-07 04:46:16 +00:00
|
|
|
let idle_timeout_s = config.presence_idle_timeout_s;
|
|
|
|
|
let offline_timeout_s = config.presence_offline_timeout_s;
|
2024-05-27 03:17:20 +00:00
|
|
|
Ok(Arc::new(Self {
|
2024-12-22 22:58:37 +00:00
|
|
|
timer_channel: loole::unbounded(),
|
2024-04-01 20:48:40 -07:00
|
|
|
timeout_remote_users: config.presence_timeout_remote_users,
|
2024-07-07 04:46:16 +00:00
|
|
|
idle_timeout: checked!(idle_timeout_s * 1_000)?,
|
|
|
|
|
offline_timeout: checked!(offline_timeout_s * 1_000)?,
|
2025-09-17 14:21:55 +05:00
|
|
|
db: Data::new(args),
|
2025-08-22 20:15:54 +05:00
|
|
|
services: args.services.clone(),
|
2025-09-02 14:26:12 +02:00
|
|
|
last_sync_seen: RwLock::new(HashMap::new()),
|
2026-01-18 16:31:36 +11:00
|
|
|
device_presence: PresenceAggregator::new(),
|
2024-05-27 03:17:20 +00:00
|
|
|
}))
|
2024-04-01 20:48:40 -07:00
|
|
|
}
|
|
|
|
|
|
2025-07-08 12:08:13 +00:00
|
|
|
async fn worker(self: Arc<Self>) -> Result {
|
2025-08-24 02:39:28 +05:00
|
|
|
// reset dormant online/away statuses to offline, and set the server user as
|
|
|
|
|
// online
|
2025-09-09 18:12:21 +05:00
|
|
|
self.unset_all_presence().await;
|
2026-01-18 16:31:36 +11:00
|
|
|
self.device_presence.clear().await;
|
2025-09-09 18:12:21 +05:00
|
|
|
_ = self
|
2025-11-01 21:37:13 +00:00
|
|
|
.maybe_ping_presence(&self.services.globals.server_user, None, &PresenceState::Online)
|
2025-09-09 18:12:21 +05:00
|
|
|
.await;
|
2025-08-24 02:39:28 +05:00
|
|
|
|
2024-12-22 22:58:37 +00:00
|
|
|
let receiver = self.timer_channel.1.clone();
|
|
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
let mut presence_timers: FuturesUnordered<_> = FuturesUnordered::new();
|
|
|
|
|
let mut timer_handles: HashMap<OwnedUserId, (u64, AbortHandle)> = HashMap::new();
|
2024-10-06 22:08:55 +00:00
|
|
|
while !receiver.is_closed() {
|
2024-07-11 21:00:30 +00:00
|
|
|
tokio::select! {
|
2026-01-18 16:31:36 +11:00
|
|
|
Some(result) = presence_timers.next() => {
|
|
|
|
|
let Ok((user_id, count)) = result else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Some((current_count, _)) = timer_handles.get(&user_id) {
|
|
|
|
|
if *current_count != count {
|
|
|
|
|
trace!(?user_id, count, current_count, "Skipping stale presence timer");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timer_handles.remove(&user_id);
|
|
|
|
|
self.process_presence_timer(&user_id, count).await.log_err().ok();
|
2024-08-08 17:18:30 +00:00
|
|
|
},
|
2024-07-11 21:00:30 +00:00
|
|
|
event = receiver.recv_async() => match event {
|
2024-10-06 22:08:55 +00:00
|
|
|
Err(_) => break,
|
2026-01-18 16:31:36 +11:00
|
|
|
Ok((user_id, timeout, count)) => {
|
|
|
|
|
debug!(
|
|
|
|
|
"Adding timer {}: {user_id} timeout:{timeout:?} count:{count}",
|
|
|
|
|
presence_timers.len()
|
|
|
|
|
);
|
|
|
|
|
if let Some((_, handle)) = timer_handles.remove(&user_id) {
|
|
|
|
|
handle.abort();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let (handle, reg) = AbortHandle::new_pair();
|
|
|
|
|
presence_timers.push(Abortable::new(
|
|
|
|
|
presence_timer(user_id.clone(), timeout, count),
|
|
|
|
|
reg,
|
|
|
|
|
));
|
|
|
|
|
timer_handles.insert(user_id, (count, handle));
|
2024-07-11 21:00:30 +00:00
|
|
|
},
|
|
|
|
|
},
|
2024-05-09 15:59:08 -07:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-06 22:08:55 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
2024-05-09 15:59:08 -07:00
|
|
|
}
|
|
|
|
|
|
2025-08-24 02:30:47 +05:00
|
|
|
async fn interrupt(&self) {
|
2025-08-24 02:39:28 +05:00
|
|
|
// set the server user as offline
|
2025-09-09 18:12:21 +05:00
|
|
|
_ = self
|
2025-11-01 21:37:13 +00:00
|
|
|
.maybe_ping_presence(
|
|
|
|
|
&self.services.globals.server_user,
|
|
|
|
|
None,
|
|
|
|
|
&PresenceState::Offline,
|
|
|
|
|
)
|
2025-09-09 18:12:21 +05:00
|
|
|
.await;
|
2025-08-24 02:39:28 +05:00
|
|
|
|
2024-12-22 22:58:37 +00:00
|
|
|
let (timer_sender, _) = &self.timer_channel;
|
|
|
|
|
if !timer_sender.is_closed() {
|
|
|
|
|
timer_sender.close();
|
2024-05-09 15:59:08 -07:00
|
|
|
}
|
2024-04-01 20:48:40 -07:00
|
|
|
}
|
|
|
|
|
|
2024-07-04 03:26:19 +00:00
|
|
|
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Service {
|
2026-01-18 16:31:36 +11:00
|
|
|
fn device_key(device_id: Option<&DeviceId>, is_remote: bool) -> aggregate::DeviceKey {
|
|
|
|
|
if is_remote {
|
|
|
|
|
return aggregate::DeviceKey::Remote;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match device_id {
|
|
|
|
|
| Some(device_id) => aggregate::DeviceKey::Device(device_id.to_owned()),
|
|
|
|
|
| None => aggregate::DeviceKey::UnknownLocal,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn schedule_presence_timer(
|
|
|
|
|
&self,
|
|
|
|
|
user_id: &UserId,
|
|
|
|
|
presence_state: &PresenceState,
|
|
|
|
|
count: u64,
|
|
|
|
|
) -> Result {
|
|
|
|
|
if !(self.timeout_remote_users || self.services.globals.user_is_local(user_id))
|
|
|
|
|
|| user_id == self.services.globals.server_user
|
|
|
|
|
{
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let timeout = match presence_state {
|
|
|
|
|
| PresenceState::Online => self.services.server.config.presence_idle_timeout_s,
|
|
|
|
|
| _ => self.services.server.config.presence_offline_timeout_s,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
self.timer_channel
|
|
|
|
|
.0
|
|
|
|
|
.send((user_id.to_owned(), Duration::from_secs(timeout), count))
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
error!("Failed to add presence timer: {}", e);
|
|
|
|
|
Error::bad_database("Failed to add presence timer")
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn apply_device_presence_update(
|
|
|
|
|
&self,
|
|
|
|
|
user_id: &UserId,
|
|
|
|
|
device_key: aggregate::DeviceKey,
|
|
|
|
|
state: &PresenceState,
|
|
|
|
|
currently_active: Option<bool>,
|
|
|
|
|
last_active_ago: Option<UInt>,
|
|
|
|
|
status_msg: Option<String>,
|
|
|
|
|
refresh_window_ms: Option<u64>,
|
|
|
|
|
) -> Result {
|
|
|
|
|
let now = tuwunel_core::utils::millis_since_unix_epoch();
|
|
|
|
|
debug!(
|
|
|
|
|
?user_id,
|
|
|
|
|
?device_key,
|
|
|
|
|
?state,
|
|
|
|
|
currently_active,
|
|
|
|
|
last_active_ago = last_active_ago.map(u64::from),
|
|
|
|
|
"Presence update received"
|
|
|
|
|
);
|
|
|
|
|
self.device_presence
|
|
|
|
|
.update(
|
|
|
|
|
user_id,
|
|
|
|
|
device_key,
|
|
|
|
|
state,
|
|
|
|
|
currently_active,
|
|
|
|
|
last_active_ago,
|
|
|
|
|
status_msg,
|
|
|
|
|
now,
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let aggregated = self
|
|
|
|
|
.device_presence
|
|
|
|
|
.aggregate(user_id, now, self.idle_timeout, self.offline_timeout)
|
|
|
|
|
.await;
|
|
|
|
|
debug!(
|
|
|
|
|
?user_id,
|
|
|
|
|
agg_state = ?aggregated.state,
|
|
|
|
|
agg_currently_active = aggregated.currently_active,
|
|
|
|
|
agg_last_active_ts = aggregated.last_active_ts,
|
|
|
|
|
agg_device_count = aggregated.device_count,
|
|
|
|
|
"Presence aggregate computed"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let last_presence = self.db.get_presence(user_id).await;
|
|
|
|
|
let (last_count, last_event) = match last_presence {
|
|
|
|
|
| Ok((count, event)) => (Some(count), Some(event)),
|
|
|
|
|
| Err(_) => (None, None),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let state_changed = match &last_event {
|
|
|
|
|
| Some(event) => event.content.presence != aggregated.state,
|
|
|
|
|
| None => true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if !state_changed {
|
|
|
|
|
if let (Some(refresh_ms), Some(event), Some(count)) =
|
|
|
|
|
(refresh_window_ms, &last_event, last_count)
|
|
|
|
|
{
|
|
|
|
|
let last_last_active_ago: u64 = event
|
|
|
|
|
.content
|
|
|
|
|
.last_active_ago
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
.into();
|
|
|
|
|
if last_last_active_ago < refresh_ms {
|
|
|
|
|
self.schedule_presence_timer(user_id, &event.content.presence, count)
|
|
|
|
|
.log_err()
|
|
|
|
|
.ok();
|
|
|
|
|
debug!(
|
|
|
|
|
?user_id,
|
|
|
|
|
?state,
|
|
|
|
|
last_last_active_ago,
|
|
|
|
|
"Skipping presence update: refresh window (timer rescheduled)"
|
|
|
|
|
);
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let fallback_status = last_event
|
|
|
|
|
.and_then(|event| event.content.status_msg)
|
|
|
|
|
.filter(|msg| !msg.is_empty());
|
|
|
|
|
let status_msg = aggregated.status_msg.or(fallback_status);
|
|
|
|
|
let last_active_ago =
|
|
|
|
|
Some(UInt::new_saturating(now.saturating_sub(aggregated.last_active_ts)));
|
|
|
|
|
|
|
|
|
|
self.set_presence(
|
|
|
|
|
user_id,
|
|
|
|
|
&aggregated.state,
|
|
|
|
|
Some(aggregated.currently_active),
|
|
|
|
|
last_active_ago,
|
|
|
|
|
status_msg,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-02 17:16:41 +02:00
|
|
|
/// record that a user has just successfully completed a /sync (or
|
|
|
|
|
/// equivalent activity)
|
2025-09-02 14:26:12 +02:00
|
|
|
pub async fn note_sync(&self, user_id: &UserId) {
|
2025-09-09 18:12:21 +05:00
|
|
|
if !self.services.config.suppress_push_when_active {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-02 14:26:12 +02:00
|
|
|
let now = tuwunel_core::utils::millis_since_unix_epoch();
|
2025-09-02 17:16:41 +02:00
|
|
|
self.last_sync_seen
|
|
|
|
|
.write()
|
|
|
|
|
.await
|
|
|
|
|
.insert(user_id.to_owned(), now);
|
2025-09-02 14:26:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns milliseconds since last observed sync for user (if any)
|
|
|
|
|
pub async fn last_sync_gap_ms(&self, user_id: &UserId) -> Option<u64> {
|
|
|
|
|
let now = tuwunel_core::utils::millis_since_unix_epoch();
|
|
|
|
|
self.last_sync_seen
|
|
|
|
|
.read()
|
|
|
|
|
.await
|
|
|
|
|
.get(user_id)
|
|
|
|
|
.map(|ts| now.saturating_sub(*ts))
|
|
|
|
|
}
|
2025-09-02 17:16:41 +02:00
|
|
|
|
2024-04-01 20:48:40 -07:00
|
|
|
/// Returns the latest presence event for the given user.
|
2024-08-08 17:18:30 +00:00
|
|
|
pub async fn get_presence(&self, user_id: &UserId) -> Result<PresenceEvent> {
|
|
|
|
|
self.db
|
|
|
|
|
.get_presence(user_id)
|
|
|
|
|
.map_ok(|(_, presence)| presence)
|
|
|
|
|
.await
|
2020-07-28 15:58:50 +02:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2025-11-01 21:37:13 +00:00
|
|
|
/// Pings the presence of the given user, setting the specified state. When
|
|
|
|
|
/// device_id is supplied.
|
2025-09-09 18:12:21 +05:00
|
|
|
pub async fn maybe_ping_presence(
|
|
|
|
|
&self,
|
|
|
|
|
user_id: &UserId,
|
2025-11-01 21:37:13 +00:00
|
|
|
device_id: Option<&DeviceId>,
|
2025-09-09 18:12:21 +05:00
|
|
|
new_state: &PresenceState,
|
|
|
|
|
) -> Result {
|
2026-01-18 16:31:36 +11:00
|
|
|
const REFRESH_TIMEOUT: u64 = 30 * 1000;
|
2024-04-04 23:21:04 -04:00
|
|
|
|
2025-09-09 18:12:21 +05:00
|
|
|
if !self.services.server.config.allow_local_presence || self.services.db.is_read_only() {
|
2026-01-18 16:31:36 +11:00
|
|
|
debug!(
|
|
|
|
|
?user_id,
|
|
|
|
|
?new_state,
|
|
|
|
|
allow_local_presence = self.services.server.config.allow_local_presence,
|
|
|
|
|
read_only = self.services.db.is_read_only(),
|
|
|
|
|
"Skipping presence ping"
|
|
|
|
|
);
|
2024-04-01 20:48:40 -07:00
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 05:38:09 +05:00
|
|
|
let update_device_seen = device_id.map_async(|device_id| {
|
|
|
|
|
self.services
|
|
|
|
|
.users
|
|
|
|
|
.update_device_last_seen(user_id, device_id, None)
|
|
|
|
|
});
|
2025-11-01 21:37:13 +00:00
|
|
|
|
2024-04-01 20:48:40 -07:00
|
|
|
let currently_active = *new_state == PresenceState::Online;
|
2026-01-18 16:31:36 +11:00
|
|
|
let set_presence = self.apply_device_presence_update(
|
2025-11-01 21:37:13 +00:00
|
|
|
user_id,
|
2026-01-18 16:31:36 +11:00
|
|
|
Self::device_key(device_id, false),
|
2025-11-01 21:37:13 +00:00
|
|
|
new_state,
|
|
|
|
|
Some(currently_active),
|
2026-01-18 16:31:36 +11:00
|
|
|
UInt::new(0),
|
|
|
|
|
None,
|
|
|
|
|
Some(REFRESH_TIMEOUT),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
debug!(
|
|
|
|
|
?user_id,
|
|
|
|
|
?new_state,
|
|
|
|
|
currently_active,
|
|
|
|
|
"Presence ping accepted"
|
2025-11-01 21:37:13 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
try_join(set_presence, update_device_seen.unwrap_or(Ok(())))
|
|
|
|
|
.map_ok(|_| ())
|
2024-08-08 17:18:30 +00:00
|
|
|
.await
|
2020-07-28 15:58:50 +02:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
/// Applies an explicit presence update for a local device.
|
|
|
|
|
pub async fn set_presence_for_device(
|
|
|
|
|
&self,
|
|
|
|
|
user_id: &UserId,
|
|
|
|
|
device_id: Option<&DeviceId>,
|
|
|
|
|
state: &PresenceState,
|
|
|
|
|
status_msg: Option<String>,
|
|
|
|
|
) -> Result {
|
|
|
|
|
let currently_active = *state == PresenceState::Online;
|
|
|
|
|
self.apply_device_presence_update(
|
|
|
|
|
user_id,
|
|
|
|
|
Self::device_key(device_id, false),
|
|
|
|
|
state,
|
|
|
|
|
Some(currently_active),
|
|
|
|
|
None,
|
|
|
|
|
status_msg,
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Applies a presence update received over federation.
|
|
|
|
|
pub async fn set_presence_from_federation(
|
|
|
|
|
&self,
|
|
|
|
|
user_id: &UserId,
|
|
|
|
|
state: &PresenceState,
|
|
|
|
|
currently_active: bool,
|
|
|
|
|
last_active_ago: UInt,
|
|
|
|
|
status_msg: Option<String>,
|
|
|
|
|
) -> Result {
|
|
|
|
|
self.apply_device_presence_update(
|
|
|
|
|
user_id,
|
|
|
|
|
Self::device_key(None, true),
|
|
|
|
|
state,
|
|
|
|
|
Some(currently_active),
|
|
|
|
|
Some(last_active_ago),
|
|
|
|
|
status_msg,
|
|
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-10 16:41:00 +02:00
|
|
|
/// Adds a presence event which will be saved until a new event replaces it.
|
2024-08-08 17:18:30 +00:00
|
|
|
pub async fn set_presence(
|
2024-12-15 00:05:47 -05:00
|
|
|
&self,
|
|
|
|
|
user_id: &UserId,
|
|
|
|
|
state: &PresenceState,
|
|
|
|
|
currently_active: Option<bool>,
|
|
|
|
|
last_active_ago: Option<UInt>,
|
2024-04-14 03:44:04 -07:00
|
|
|
status_msg: Option<String>,
|
2025-07-08 12:08:13 +00:00
|
|
|
) -> Result {
|
2024-04-14 03:44:04 -07:00
|
|
|
let presence_state = match state.as_str() {
|
2024-12-15 00:05:47 -05:00
|
|
|
| "" => &PresenceState::Offline, // default an empty string to 'offline'
|
|
|
|
|
| &_ => state,
|
2024-04-14 03:44:04 -07:00
|
|
|
};
|
|
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
let count = self
|
|
|
|
|
.db
|
2024-08-08 17:18:30 +00:00
|
|
|
.set_presence(user_id, presence_state, currently_active, last_active_ago, status_msg)
|
|
|
|
|
.await?;
|
2024-04-01 20:48:40 -07:00
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
if let Some(count) = count {
|
2026-01-21 05:00:53 +11:00
|
|
|
let is_local = self.services.globals.user_is_local(user_id);
|
|
|
|
|
let is_server_user = user_id == self.services.globals.server_user;
|
|
|
|
|
let allow_timeout = self.timeout_remote_users || is_local;
|
2026-01-18 16:31:36 +11:00
|
|
|
|
2026-01-21 05:00:53 +11:00
|
|
|
if allow_timeout && !is_server_user {
|
2026-01-18 16:31:36 +11:00
|
|
|
self.schedule_presence_timer(user_id, presence_state, count)?;
|
|
|
|
|
}
|
2024-04-01 20:48:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
2023-07-10 16:41:00 +02:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2023-07-10 16:41:00 +02:00
|
|
|
/// Removes the presence record for the given user from the database.
|
2024-04-22 23:48:57 -04:00
|
|
|
///
|
|
|
|
|
/// TODO: Why is this not used?
|
|
|
|
|
#[allow(dead_code)]
|
2024-12-15 00:05:47 -05:00
|
|
|
pub async fn remove_presence(&self, user_id: &UserId) {
|
|
|
|
|
self.db.remove_presence(user_id).await;
|
|
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2025-01-08 18:42:46 +08:00
|
|
|
// Unset online/unavailable presence to offline on startup
|
2025-09-09 18:12:21 +05:00
|
|
|
async fn unset_all_presence(&self) {
|
|
|
|
|
if !self.services.server.config.allow_local_presence || self.services.db.is_read_only() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-10 23:51:08 -05:00
|
|
|
let _cork = self.services.db.cork();
|
|
|
|
|
|
2025-01-08 18:42:46 +08:00
|
|
|
for user_id in &self
|
|
|
|
|
.services
|
|
|
|
|
.users
|
|
|
|
|
.list_local_users()
|
|
|
|
|
.map(UserId::to_owned)
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.await
|
|
|
|
|
{
|
|
|
|
|
let presence = self.db.get_presence(user_id).await;
|
|
|
|
|
|
|
|
|
|
let presence = match presence {
|
|
|
|
|
| Ok((_, ref presence)) => &presence.content,
|
2025-01-10 23:51:08 -05:00
|
|
|
| _ => continue,
|
2025-01-08 18:42:46 +08:00
|
|
|
};
|
|
|
|
|
|
2025-01-10 23:51:08 -05:00
|
|
|
if !matches!(
|
|
|
|
|
presence.presence,
|
|
|
|
|
PresenceState::Unavailable | PresenceState::Online | PresenceState::Busy
|
|
|
|
|
) {
|
|
|
|
|
trace!(?user_id, ?presence, "Skipping user");
|
2025-01-10 23:51:51 +08:00
|
|
|
continue;
|
2025-01-08 18:42:46 +08:00
|
|
|
}
|
|
|
|
|
|
2025-01-10 23:51:08 -05:00
|
|
|
trace!(?user_id, ?presence, "Resetting presence to offline");
|
|
|
|
|
|
|
|
|
|
_ = self
|
|
|
|
|
.set_presence(
|
|
|
|
|
user_id,
|
|
|
|
|
&PresenceState::Offline,
|
|
|
|
|
Some(false),
|
|
|
|
|
presence.last_active_ago,
|
|
|
|
|
presence.status_msg.clone(),
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.inspect_err(|e| {
|
|
|
|
|
debug_warn!(
|
|
|
|
|
?presence,
|
|
|
|
|
"{user_id} has invalid presence in database and failed to reset it to \
|
|
|
|
|
offline: {e}"
|
|
|
|
|
);
|
|
|
|
|
});
|
2025-01-08 18:42:46 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-10 16:41:00 +02:00
|
|
|
/// Returns the most recent presence updates that happened after the event
|
|
|
|
|
/// with id `since`.
|
2024-12-15 00:05:47 -05:00
|
|
|
pub fn presence_since(
|
|
|
|
|
&self,
|
|
|
|
|
since: u64,
|
2025-07-26 04:59:00 +00:00
|
|
|
to: Option<u64>,
|
2024-12-15 00:05:47 -05:00
|
|
|
) -> impl Stream<Item = (&UserId, u64, &[u8])> + Send + '_ {
|
2025-07-26 04:59:00 +00:00
|
|
|
self.db.presence_since(since, to)
|
2023-07-10 16:41:00 +02:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2024-08-08 17:18:30 +00:00
|
|
|
#[inline]
|
2024-12-15 00:05:47 -05:00
|
|
|
pub async fn from_json_bytes_to_event(
|
|
|
|
|
&self,
|
|
|
|
|
bytes: &[u8],
|
|
|
|
|
user_id: &UserId,
|
|
|
|
|
) -> Result<PresenceEvent> {
|
2024-07-18 06:37:47 +00:00
|
|
|
let presence = Presence::from_json_bytes(bytes)?;
|
2024-08-08 17:18:30 +00:00
|
|
|
let event = presence
|
|
|
|
|
.to_presence_event(user_id, &self.services.users)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
Ok(event)
|
2024-07-18 06:37:47 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
async fn process_presence_timer(&self, user_id: &OwnedUserId, expected_count: u64) -> Result {
|
|
|
|
|
let (current_count, presence) = match self.db.get_presence_raw(user_id).await {
|
|
|
|
|
| Ok(presence) => presence,
|
|
|
|
|
| Err(_) => return Ok(()),
|
|
|
|
|
};
|
2023-07-10 16:41:00 +02:00
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
if current_count != expected_count {
|
|
|
|
|
trace!(
|
|
|
|
|
?user_id,
|
|
|
|
|
expected_count,
|
|
|
|
|
current_count,
|
|
|
|
|
"Skipping stale presence timer"
|
|
|
|
|
);
|
|
|
|
|
return Ok(());
|
2024-07-07 04:46:16 +00:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
let presence_state = presence.state().clone();
|
|
|
|
|
let now = tuwunel_core::utils::millis_since_unix_epoch();
|
|
|
|
|
let aggregated = self
|
|
|
|
|
.device_presence
|
|
|
|
|
.aggregate(user_id, now, self.idle_timeout, self.offline_timeout)
|
|
|
|
|
.await;
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
if aggregated.device_count == 0 {
|
|
|
|
|
let last_active_ago =
|
|
|
|
|
Some(UInt::new_saturating(now.saturating_sub(presence.last_active_ts())));
|
|
|
|
|
let status_msg = presence.status_msg();
|
|
|
|
|
let new_state = match (&presence_state, last_active_ago.map(u64::from)) {
|
|
|
|
|
| (PresenceState::Online, Some(ago)) if ago >= self.idle_timeout =>
|
|
|
|
|
Some(PresenceState::Unavailable),
|
|
|
|
|
| (PresenceState::Unavailable, Some(ago)) if ago >= self.offline_timeout =>
|
|
|
|
|
Some(PresenceState::Offline),
|
|
|
|
|
| _ => None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
debug!(
|
|
|
|
|
"Processed presence timer for user '{user_id}': Old state = {presence_state}, New \
|
|
|
|
|
state = {new_state:?}"
|
|
|
|
|
);
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
if let Some(new_state) = new_state {
|
|
|
|
|
self.set_presence(
|
|
|
|
|
user_id,
|
|
|
|
|
&new_state,
|
|
|
|
|
Some(false),
|
|
|
|
|
last_active_ago,
|
|
|
|
|
status_msg,
|
|
|
|
|
)
|
2024-08-08 17:18:30 +00:00
|
|
|
.await?;
|
2026-01-18 16:31:36 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if aggregated.state == presence_state {
|
|
|
|
|
self.schedule_presence_timer(user_id, &presence_state, current_count)
|
|
|
|
|
.log_err()
|
|
|
|
|
.ok();
|
|
|
|
|
return Ok(());
|
2024-07-07 04:46:16 +00:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
let status_msg = aggregated.status_msg.or_else(|| presence.status_msg());
|
|
|
|
|
let last_active_ago =
|
|
|
|
|
Some(UInt::new_saturating(now.saturating_sub(aggregated.last_active_ts)));
|
|
|
|
|
|
|
|
|
|
self.set_presence(
|
|
|
|
|
user_id,
|
|
|
|
|
&aggregated.state,
|
|
|
|
|
Some(aggregated.currently_active),
|
|
|
|
|
last_active_ago,
|
|
|
|
|
status_msg,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
2024-07-07 04:46:16 +00:00
|
|
|
Ok(())
|
2023-07-10 16:41:00 +02:00
|
|
|
}
|
2024-07-07 04:46:16 +00:00
|
|
|
}
|
2024-03-05 19:48:54 -05:00
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
async fn presence_timer(user_id: OwnedUserId, timeout: Duration, count: u64) -> TimerFired {
|
2024-07-07 04:46:16 +00:00
|
|
|
sleep(timeout).await;
|
|
|
|
|
|
2026-01-18 16:31:36 +11:00
|
|
|
(user_id, count)
|
2020-05-03 17:25:31 +02:00
|
|
|
}
|