Move Presence class to its own python file

It's not a ModelItem
This commit is contained in:
miruka 2020-07-18 18:33:57 -04:00
parent 692c78f398
commit 1b5a09c052
5 changed files with 124 additions and 107 deletions

View File

@ -18,9 +18,10 @@ from .matrix_client import MatrixClient
from .media_cache import MediaCache from .media_cache import MediaCache
from .models import SyncId from .models import SyncId
from .models.filters import FieldSubstringFilter from .models.filters import FieldSubstringFilter
from .models.items import Account, Presence from .models.items import Account
from .models.model import Model from .models.model import Model
from .models.model_store import ModelStore from .models.model_store import ModelStore
from .presence import Presence
from .user_files import Accounts, History, Theme, UISettings, UIState from .user_files import Accounts, History, Theme, UISettings, UIState
# Logging configuration # Logging configuration

View File

@ -42,10 +42,11 @@ from .errors import (
from .html_markdown import HTML_PROCESSOR as HTML from .html_markdown import HTML_PROCESSOR as HTML
from .media_cache import Media, Thumbnail from .media_cache import Media, Thumbnail
from .models.items import ( 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 .models.model_store import ModelStore
from .nio_callbacks import NioCallbacks from .nio_callbacks import NioCallbacks
from .presence import Presence
from .pyotherside_events import AlertRequested, LoopException from .pyotherside_events import AlertRequested, LoopException
if TYPE_CHECKING: if TYPE_CHECKING:

View File

@ -10,8 +10,10 @@ from typing import Any, Dict, List, Optional, Tuple, Type, Union
from uuid import UUID from uuid import UUID
import lxml # nosec import lxml # nosec
import nio import nio
from ..presence import Presence
from ..utils import AutoStrEnum, auto from ..utils import AutoStrEnum, auto
from .model_item import ModelItem from .model_item import ModelItem
@ -19,12 +21,6 @@ OptionalExceptionType = Union[Type[None], Type[Exception]]
ZERO_DATE = datetime.fromtimestamp(0) ZERO_DATE = datetime.fromtimestamp(0)
PRESENCE_ORDER: Dict[str, int] = {
"online": 0,
"unavailable": 1,
"offline": 2,
}
class TypeSpecifier(AutoStrEnum): class TypeSpecifier(AutoStrEnum):
"""Enum providing clarification of purpose for some matrix events.""" """Enum providing clarification of purpose for some matrix events."""
@ -34,104 +30,6 @@ class TypeSpecifier(AutoStrEnum):
MembershipChange = auto() 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 @dataclass
class Account(ModelItem): class Account(ModelItem):
"""A logged in matrix account.""" """A logged in matrix account."""

View File

@ -12,7 +12,8 @@ from urllib.parse import quote
import nio import nio
from .html_markdown import HTML_PROCESSOR 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 .pyotherside_events import DevicesUpdated
from .utils import classes_defined_in, plain2html from .utils import classes_defined_in, plain2html

116
src/backend/presence.py Normal file
View File

@ -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)