From 1b5a09c05297d9666102e63b4e5840cd133ec95d Mon Sep 17 00:00:00 2001 From: miruka Date: Sat, 18 Jul 2020 18:33:57 -0400 Subject: [PATCH] Move Presence class to its own python file It's not a ModelItem --- src/backend/backend.py | 3 +- src/backend/matrix_client.py | 3 +- src/backend/models/items.py | 106 +------------------------------- src/backend/nio_callbacks.py | 3 +- src/backend/presence.py | 116 +++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 107 deletions(-) create mode 100644 src/backend/presence.py diff --git a/src/backend/backend.py b/src/backend/backend.py index 2f010ba0..6eccdd24 100644 --- a/src/backend/backend.py +++ b/src/backend/backend.py @@ -18,9 +18,10 @@ from .matrix_client import MatrixClient from .media_cache import MediaCache from .models import SyncId from .models.filters import FieldSubstringFilter -from .models.items import Account, Presence +from .models.items import Account from .models.model import Model from .models.model_store import ModelStore +from .presence import Presence from .user_files import Accounts, History, Theme, UISettings, UIState # Logging configuration diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index 3e1c706c..4957c66f 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -42,10 +42,11 @@ from .errors import ( from .html_markdown import HTML_PROCESSOR as HTML from .media_cache import Media, Thumbnail from .models.items import ( - ZERO_DATE, Account, Event, Member, Presence, Room, Upload, UploadStatus, + ZERO_DATE, Account, Event, Member, Room, Upload, UploadStatus, ) from .models.model_store import ModelStore from .nio_callbacks import NioCallbacks +from .presence import Presence from .pyotherside_events import AlertRequested, LoopException if TYPE_CHECKING: diff --git a/src/backend/models/items.py b/src/backend/models/items.py index 12b6dbf1..b8027d78 100644 --- a/src/backend/models/items.py +++ b/src/backend/models/items.py @@ -10,8 +10,10 @@ from typing import Any, Dict, List, Optional, Tuple, Type, Union from uuid import UUID import lxml # nosec + import nio +from ..presence import Presence from ..utils import AutoStrEnum, auto from .model_item import ModelItem @@ -19,12 +21,6 @@ OptionalExceptionType = Union[Type[None], Type[Exception]] ZERO_DATE = datetime.fromtimestamp(0) -PRESENCE_ORDER: Dict[str, int] = { - "online": 0, - "unavailable": 1, - "offline": 2, -} - class TypeSpecifier(AutoStrEnum): """Enum providing clarification of purpose for some matrix events.""" @@ -34,104 +30,6 @@ class TypeSpecifier(AutoStrEnum): MembershipChange = auto() -@dataclass -class Presence: - """Represents a single matrix user's presence fields. - - These objects are stored in `Backend.presences`, indexed by user ID. - It must only be instanced when receiving a `PresenceEvent` or - registering an `Account` model item. - - When receiving a `PresenceEvent`, we get or create a `Presence` object in - `Backend.presences` for the targeted user. If the user is registered in any - room, add its `Member` model item to `members`. Finally, update every - `Member` presence fields inside `members`. - - When a room member is registered, we try to find a `Presence` in - `Backend.presences` for that user ID. If found, the `Member` item is added - to `members`. - - When an Account model is registered, we create a `Presence` in - `Backend.presences` for the accountu's user ID whether the server supports - presence or not (we cannot know yet at this point), - and assign that `Account` to the `Presence.account` field. - - Special attributes: - members: A `{room_id: Member}` dict for storing room members related to - this `Presence`. As each room has its own `Member`s objects, we - have to keep track of their presence fields. `Member`s are indexed - by room ID. - - account: `Account` related to this `Presence`, if any. Should be - assigned when client starts (`MatrixClient._start()`) and - cleared when client stops (`MatrixClient._start()`). - """ - - class State(AutoStrEnum): - offline = auto() # can mean offline, invisible or unknwon - unavailable = auto() - online = auto() - invisible = auto() - - echo_unavailable = auto() - echo_online = auto() - echo_invisible = auto() - - def __lt__(self, other: "Presence.State") -> bool: - return PRESENCE_ORDER[self.value] < PRESENCE_ORDER[other.value] - - - presence: State = State.offline - currently_active: bool = False - last_active_at: datetime = ZERO_DATE - status_msg: str = "" - - members: Dict[str, "Member"] = field(default_factory=dict) - account: Optional["Account"] = None - - def update_members(self) -> None: - """Update presence fields of every `M̀ember` in `members`. - - Currently it is only called when receiving a `PresenceEvent` and when - registering room members. - """ - - for member in self.members.values(): - member.set_fields( - presence = self.presence, - status_msg = self.status_msg, - last_active_at = self.last_active_at, - currently_active = self.currently_active, - ) - - def update_account(self) -> None: - """Update presence fields of `Account` related to this `Presence`.""" - - # Do not update if account is changing to invisible. - # When setting presence to invisible, the server will give us a - # presence event telling us we are offline, but we do not want to set - # account presence to offline. - if ( - not self.account or - self.presence == self.State.offline and - self.account.presence != self.State.echo_invisible - ): - return - - fields: Dict[str, Any] = {} - - if self.account.presence == self.State.echo_invisible: - fields["presence"] = self.State.invisible - else: - fields["presence"] = self.presence - fields["status_msg"] = self.status_msg - - fields["last_active_at"] = self.last_active_at - fields["currently_active"] = self.currently_active - - self.account.set_fields(**fields) - - @dataclass class Account(ModelItem): """A logged in matrix account.""" diff --git a/src/backend/nio_callbacks.py b/src/backend/nio_callbacks.py index 2bae2862..4b389f40 100644 --- a/src/backend/nio_callbacks.py +++ b/src/backend/nio_callbacks.py @@ -12,7 +12,8 @@ from urllib.parse import quote import nio from .html_markdown import HTML_PROCESSOR -from .models.items import Presence, TypeSpecifier +from .models.items import TypeSpecifier +from .presence import Presence from .pyotherside_events import DevicesUpdated from .utils import classes_defined_in, plain2html diff --git a/src/backend/presence.py b/src/backend/presence.py new file mode 100644 index 00000000..daef679f --- /dev/null +++ b/src/backend/presence.py @@ -0,0 +1,116 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later + +from dataclasses import dataclass, field +from datetime import datetime +from typing import TYPE_CHECKING, Any, Dict, Optional + +from .utils import AutoStrEnum, auto + +if TYPE_CHECKING: + from .models.items import Account, Member + +ORDER: Dict[str, int] = { + "online": 0, + "unavailable": 1, + "offline": 2, +} + + +@dataclass +class Presence: + """Represents a single matrix user's presence fields. + + These objects are stored in `Backend.presences`, indexed by user ID. + It must only be instanced when receiving a `PresenceEvent` or + registering an `Account` model item. + + When receiving a `PresenceEvent`, we get or create a `Presence` object in + `Backend.presences` for the targeted user. If the user is registered in any + room, add its `Member` model item to `members`. Finally, update every + `Member` presence fields inside `members`. + + When a room member is registered, we try to find a `Presence` in + `Backend.presences` for that user ID. If found, the `Member` item is added + to `members`. + + When an Account model is registered, we create a `Presence` in + `Backend.presences` for the accountu's user ID whether the server supports + presence or not (we cannot know yet at this point), + and assign that `Account` to the `Presence.account` field. + + Special attributes: + members: A `{room_id: Member}` dict for storing room members related to + this `Presence`. As each room has its own `Member`s objects, we + have to keep track of their presence fields. `Member`s are indexed + by room ID. + + account: `Account` related to this `Presence`, if any. Should be + assigned when client starts (`MatrixClient._start()`) and + cleared when client stops (`MatrixClient._start()`). + """ + + + class State(AutoStrEnum): + offline = auto() # can mean offline, invisible or unknwon + unavailable = auto() + online = auto() + invisible = auto() + + echo_unavailable = auto() + echo_online = auto() + echo_invisible = auto() + + def __lt__(self, other: "Presence.State") -> bool: + return ORDER[self.value] < ORDER[other.value] + + + presence: State = State.offline + currently_active: bool = False + last_active_at: datetime = datetime.fromtimestamp(0) + status_msg: str = "" + + members: Dict[str, "Member"] = field(default_factory=dict) + account: Optional["Account"] = None + + + def update_members(self) -> None: + """Update presence fields of every `M̀ember` in `members`. + + Currently it is only called when receiving a `PresenceEvent` and when + registering room members. + """ + + for member in self.members.values(): + member.set_fields( + presence = self.presence, + status_msg = self.status_msg, + last_active_at = self.last_active_at, + currently_active = self.currently_active, + ) + + def update_account(self) -> None: + """Update presence fields of `Account` related to this `Presence`.""" + + # Do not update if account is changing to invisible. + # When setting presence to invisible, the server will give us a + # presence event telling us we are offline, but we do not want to set + # account presence to offline. + if ( + not self.account or + self.presence == self.State.offline and + self.account.presence != self.State.echo_invisible + ): + return + + fields: Dict[str, Any] = {} + + if self.account.presence == self.State.echo_invisible: + fields["presence"] = self.State.invisible + else: + fields["presence"] = self.presence + fields["status_msg"] = self.status_msg + + fields["last_active_at"] = self.last_active_at + fields["currently_active"] = self.currently_active + + self.account.set_fields(**fields)