Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Merge device presence states to update the user's presence state.
Browse files Browse the repository at this point in the history
  • Loading branch information
clokep committed Aug 23, 2023
1 parent e92eabc commit d9456e3
Showing 1 changed file with 54 additions and 7 deletions.
61 changes: 54 additions & 7 deletions synapse/handlers/presence.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ def __init__(self, hs: "HomeServer"):
self.VALID_PRESENCE += (PresenceState.BUSY,)

active_presence = self.store.take_presence_startup_info()
# The combined status across all user devices.
self.user_to_current_state = {state.user_id: state for state in active_presence}

@abc.abstractmethod
Expand Down Expand Up @@ -1077,11 +1078,13 @@ async def bump_presence_active_time(
if device_state.state == PresenceState.UNAVAILABLE:
device_state.state = PresenceState.ONLINE

# Update the user state, this will always update last_active_ts and
# might update the presence state.
prev_state = await self.current_state_for_user(user_id)

new_fields: Dict[str, Any] = {"last_active_ts": now}
if prev_state.state == PresenceState.UNAVAILABLE:
new_fields["state"] = PresenceState.ONLINE
new_fields: Dict[str, Any] = {
"last_active_ts": now,
"state": _combine_device_states(devices.values()),
}

await self._update_states([prev_state.copy_and_replace(**new_fields)])

Expand Down Expand Up @@ -1225,19 +1228,20 @@ async def update_external_syncs_clear(self, process_id: str) -> None:
time_now_ms = self.clock.time_msec()

# Mark each device as having a last sync time.
updated_users = set()
for user_id, device_id in process_presence:
device_state = self._user_to_device_to_current_state.setdefault(
user_id, {}
).setdefault(
device_id, UserDevicePresenceState.default(user_id, device_id)
)

device_state.last_sync_ts = time_now_ms
updated_users.add(user_id)

# Update each user (and insert into the appropriate timers to check if
# they've gone offline).
prev_states = await self.current_state_for_users(
{user_id for user_id, device_id in process_presence}
)
prev_states = await self.current_state_for_users(updated_users)
await self._update_states(
[
prev_state.copy_and_replace(last_user_sync_ts=time_now_ms)
Expand Down Expand Up @@ -1370,6 +1374,9 @@ async def set_state(
if is_sync:
device_state.last_sync_ts = now

# Based on the state of each user's device calculate the new presence state.
presence = _combine_device_states(devices.values())

new_fields = {"state": presence}

if not ignore_status_msg:
Expand Down Expand Up @@ -2129,6 +2136,46 @@ def handle_update(
return new_state, persist_and_notify, federation_ping


PRESENCE_BY_PRIORITY = {
PresenceState.BUSY: 4,
PresenceState.ONLINE: 3,
PresenceState.UNAVAILABLE: 2,
PresenceState.OFFLINE: 1,
}


def _combine_device_states(
device_states: Iterable[UserDevicePresenceState],
) -> str:
"""
Find the device to use presence information from.
Orders devices by priority, then last_active_ts.
Args:
device_states: An iterable of device presence states
Return:
The combined presence state.
"""

# Based on (all) the user's devices calculate the new presence state.
presence = PresenceState.OFFLINE
last_active_ts = -1

# Find the device to use the presence state of based on the presence priority,
# but tie-break with how recently the device has been seen.
for device_state in device_states:
if (PRESENCE_BY_PRIORITY[device_state.state], device_state.last_active_ts) > (
PRESENCE_BY_PRIORITY[presence],
last_active_ts,
):
presence = device_state.state
last_active_ts = device_state.last_active_ts

return presence


async def get_interested_parties(
store: DataStore, presence_router: PresenceRouter, states: List[UserPresenceState]
) -> Tuple[Dict[str, List[UserPresenceState]], Dict[str, List[UserPresenceState]]]:
Expand Down

0 comments on commit d9456e3

Please sign in to comment.