Fix presence loop with unavailable/offline

Yes, again.
Also removes echo presence states to simplify things.
This commit is contained in:
miruka 2021-07-26 00:01:02 -04:00
parent 47cfd7c7b0
commit 94bf41dc3e
5 changed files with 49 additions and 85 deletions

View File

@ -388,9 +388,7 @@ class Backend:
await client.update_account_unread_counts() await client.update_account_unread_counts()
if account.presence not in [ if account.presence not in [
Presence.State.echo_invisible, Presence.State.invisible, Presence.State.offline,
Presence.State.invisible,
Presence.State.offline,
]: ]:
await client.update_receipt_marker(room_id, event_id) await client.update_receipt_marker(room_id, event_id)

View File

@ -307,7 +307,7 @@ class MatrixClient(nio.AsyncClient):
) )
# TODO: be able to set presence before logging in # TODO: be able to set presence before logging in
item.set_fields(presence=Presence.State.echo_online, connecting=True) item.set_fields(presence=Presence.State.online, connecting=True)
self._presence = "online" self._presence = "online"
self.start_task = asyncio.ensure_future(self._start()) self.start_task = asyncio.ensure_future(self._start())
@ -327,9 +327,6 @@ class MatrixClient(nio.AsyncClient):
account = self.models["accounts"][user_id] account = self.models["accounts"][user_id]
self._presence = "offline" if state == "invisible" else state self._presence = "offline" if state == "invisible" else state
if state in ("online", "unavailable"):
state = f"echo_{state}"
account.set_fields( account.set_fields(
presence=Presence.State(state), status_msg=status_msg, presence=Presence.State(state), status_msg=status_msg,
) )
@ -1386,7 +1383,6 @@ class MatrixClient(nio.AsyncClient):
return return
if self.models["accounts"][self.user_id].presence not in [ if self.models["accounts"][self.user_id].presence not in [
Presence.State.echo_invisible,
Presence.State.invisible, Presence.State.invisible,
Presence.State.offline, Presence.State.offline,
]: ]:
@ -1596,15 +1592,14 @@ class MatrixClient(nio.AsyncClient):
account = self.models["accounts"][self.user_id] account = self.models["accounts"][self.user_id]
call_presence_api = True call_presence_api = True
new_presence = presence self._presence = "offline" if presence == "invisible" else presence
for_server = "offline" if presence == "invisible" else presence
if status_msg is None: if status_msg is None:
status_msg = account.status_msg status_msg = account.status_msg
# Starting/stopping client if current/new presence is offline # Starting/stopping client if current/new presence is offline
if new_presence == "offline": if presence == "offline":
if account.presence == Presence.State.offline: if account.presence == Presence.State.offline:
return return
@ -1620,7 +1615,6 @@ class MatrixClient(nio.AsyncClient):
# We might receive a recent status_msg set from another client on # We might receive a recent status_msg set from another client on
# startup, so don't try to set a new one immediatly. # startup, so don't try to set a new one immediatly.
# Presence though will be sent on first sync. # Presence though will be sent on first sync.
self._presence = for_server
call_presence_api = False call_presence_api = False
account.connecting = True account.connecting = True
self.start_task = asyncio.ensure_future(self._start()) self.start_task = asyncio.ensure_future(self._start())
@ -1629,9 +1623,9 @@ class MatrixClient(nio.AsyncClient):
if ( if (
Presence.State(presence) != account.presence and Presence.State(presence) != account.presence and
new_presence != "offline" presence != "offline"
): ):
account.presence = Presence.State("echo_" + new_presence) account.presence = Presence.State(presence)
# Saving new details in accounts.json # Saving new details in accounts.json
@ -1639,7 +1633,7 @@ class MatrixClient(nio.AsyncClient):
account.save_presence = True account.save_presence = True
await self.backend.saved_accounts.set( await self.backend.saved_accounts.set(
self.user_id, presence=new_presence, status_msg=status_msg, self.user_id, presence=presence, status_msg=status_msg,
) )
else: else:
account.save_presence = False account.save_presence = False
@ -1648,7 +1642,7 @@ class MatrixClient(nio.AsyncClient):
if call_presence_api: if call_presence_api:
account.status_msg = status_msg account.status_msg = status_msg
await super().set_presence(for_server, status_msg) await super().set_presence(self._presence, status_msg)
async def import_keys(self, infile: str, passphrase: str) -> None: async def import_keys(self, infile: str, passphrase: str) -> None:

View File

@ -870,20 +870,29 @@ class NioCallbacks:
async def onPresenceEvent( async def onPresenceEvent(
self, ev: Union[nio.PresenceEvent, nio.PresenceGetResponse], self, ev: Union[nio.PresenceEvent, nio.PresenceGetResponse],
) -> None: ) -> None:
# Servers that send presence events support presence # Servers that send presence events support presence
self.models["accounts"][self.client.user_id].presence_support = True self.models["accounts"][self.client.user_id].presence_support = True
account = self.models["accounts"].get(ev.user_id) account = self.models["accounts"].get(ev.user_id)
presence = self.client.backend.presences.get(ev.user_id, Presence()) presence = self.client.backend.presences.get(ev.user_id, Presence())
invisible = False
if account: if account:
client = self.client.backend.clients[ev.user_id] invisible = account.presence == Presence.State.invisible
client = self.client.backend.clients[ev.user_id]
# Synapse is stupid enough to return an older presence state on # Synapse is stupid enough to return an older presence state on
# sync, which then causes a never-ending loop of presence cycling. # sync, which then causes a never-ending loop of presence cycling.
# Let's hope they didn't screw up the get_presence API too: # Let's hope they didn't screw up the get_presence API too:
ev = await client.get_presence(ev.user_id) ev = await client.get_presence(ev.user_id)
invisible = account and "invisible" in account.presence.value if ev.presence == "offline" and not invisible:
to_set = account.presence.value
await client.set_presence(to_set, account.status_msg)
return
elif not (invisible and ev.presence != "offline"):
client._presence = ev.presence
if invisible and ev.presence == "offline": if invisible and ev.presence == "offline":
presence.presence = Presence.State.invisible presence.presence = Presence.State.invisible
@ -891,7 +900,15 @@ class NioCallbacks:
presence.presence = Presence.State(ev.presence) presence.presence = Presence.State(ev.presence)
presence.currently_active = ev.currently_active or False presence.currently_active = ev.currently_active or False
presence.status_msg = ev.status_msg or ""
# Restore status msg lost from server due to e.g. getting offline
if account and account.status_msg and not ev.status_msg:
if invisible:
presence.status_msg = account.status_msg
else:
await client.set_presence(ev.presence, account.status_msg)
else:
presence.status_msg = ev.status_msg or ""
if ev.last_active_ago: if ev.last_active_ago:
presence.last_active_at = datetime.now() - timedelta( presence.last_active_at = datetime.now() - timedelta(
@ -909,58 +926,21 @@ class NioCallbacks:
presence.update_members() presence.update_members()
# If presence event represents a change for one of our account if not account:
if account and account.presence != Presence.State.offline:
client = self.client.backend.clients[ev.user_id]
# Ignore cases where we send a new presence to the server, but it
# returns an older state that doesn't match due to lag:
if (
account.presence == Presence.State.echo_invisible and
ev.presence != Presence.State.offline.value
) or (
account.presence == Presence.State.echo_unavailable and
ev.presence != Presence.State.unavailable.value
) or (
account.presence == Presence.State.echo_online and
ev.presence != Presence.State.online.value
):
return
# Do not fight back presence from other clients, unless server says
# we're offline, which happens if another client disconnected or we
# had a long connection issue. Note that this makes invisibility
# only possible if we're the only client using the account, or the
# other clients are invisible/offline themselves.
if ev.presence != Presence.State.offline.value:
client._presence = ev.presence
# Restore status msg lost from server due to e.g. getting offline
if not ev.status_msg and account.status_msg:
if invisible:
presence.status_msg = account.status_msg
else:
await client.set_presence(ev.presence, account.status_msg)
# Save the presence to be restored next time we restart application
if account.save_presence:
status_msg = presence.status_msg
state = presence.presence
if account.presence == Presence.State.echo_invisible:
status_msg = account.status_msg
state = Presence.State.invisible
elif state == Presence.State.echo_online:
state = Presence.State.online
elif state == Presence.State.echo_unavailable:
state = Presence.State.unavailable
await self.client.backend.saved_accounts.set(
user_id = ev.user_id,
status_msg = status_msg,
presence = state.value,
)
presence.update_account()
else:
self.client.backend.presences[ev.user_id] = presence self.client.backend.presences[ev.user_id] = presence
return
client = self.client.backend.clients[ev.user_id]
# Save the presence to be restored next time we restart application
if account.save_presence:
status_msg = presence.status_msg
state = presence.presence
await self.client.backend.saved_accounts.set(
user_id = ev.user_id,
status_msg = status_msg,
presence = state.value,
)
presence.update_account()

View File

@ -12,12 +12,9 @@ if TYPE_CHECKING:
ORDER: Dict[str, int] = { ORDER: Dict[str, int] = {
"online": 0, "online": 0,
"echo_online": 1, "unavailable": 1,
"unavailable": 2, "invisible": 2,
"echo_unavailable": 3, "offline": 3,
"invisible": 4,
"echo_invisible": 5,
"offline": 6,
} }
@ -60,10 +57,6 @@ class Presence:
online = auto() online = auto()
invisible = auto() invisible = auto()
echo_unavailable = auto()
echo_online = auto()
echo_invisible = auto()
def __lt__(self, other: "Presence.State") -> bool: def __lt__(self, other: "Presence.State") -> bool:
return ORDER[self.value] < ORDER[other.value] return ORDER[self.value] < ORDER[other.value]

View File

@ -13,8 +13,7 @@ Rectangle {
implicitHeight: width implicitHeight: width
radius: width / 2 radius: width / 2
opacity: opacity: theme.controls.presence.opacity
theme.controls.presence.opacity * (presence.includes("echo") ? 0.5 : 1)
color: color:
presence.includes("online") ? presence.includes("online") ?