1d0cce402e
For any name not found in rooms data, rely on new nio.HttpClient.get_displayname() function to get and cache it, e.g. for our own name if no room is joined and past events from users who left the room. @futurize now returns PyQtFuture objects, wrapper for the concurrent.futures.Future objects that can be used from QML, to ensure name retrieval does not block the GUI.
186 lines
6.0 KiB
Python
186 lines
6.0 KiB
Python
# Copyright 2019 miruka
|
|
# This file is part of harmonyqml, licensed under GPLv3.
|
|
|
|
from threading import Lock
|
|
from typing import Any, Deque, Dict, List, Optional
|
|
|
|
from PyQt5.QtCore import QDateTime, QObject, pyqtBoundSignal
|
|
|
|
import nio
|
|
|
|
from .backend import Backend
|
|
from .client import Client
|
|
from .model.items import Room, RoomEvent, User
|
|
|
|
|
|
class SignalManager(QObject):
|
|
_event_handling_lock: Lock = Lock()
|
|
|
|
def __init__(self, backend: Backend) -> None:
|
|
super().__init__(parent=backend)
|
|
self.backend = backend
|
|
|
|
self.last_room_events: Deque[str] = Deque(maxlen=1000)
|
|
self._events_in_transfer: int = 0
|
|
|
|
cm = self.backend.clientManager
|
|
cm.clientAdded.connect(self.onClientAdded)
|
|
cm.clientDeleted.connect(self.onClientDeleted)
|
|
|
|
|
|
def onClientAdded(self, client: Client) -> None:
|
|
self.connectClient(client)
|
|
self.backend.models.accounts.append(User(
|
|
user_id = client.userID,
|
|
display_name = self.backend.getUserDisplayName(client.userID),
|
|
))
|
|
|
|
|
|
def onClientDeleted(self, user_id: str) -> None:
|
|
accs = self.backend.models.accounts
|
|
del accs[accs.indexWhere("user_id", user_id)]
|
|
|
|
|
|
def connectClient(self, client: Client) -> None:
|
|
for name in dir(client):
|
|
attr = getattr(client, name)
|
|
|
|
if isinstance(attr, pyqtBoundSignal):
|
|
def onSignal(*args, name=name) -> None:
|
|
func = getattr(self, f"on{name[0].upper()}{name[1:]}")
|
|
func(client, *args)
|
|
|
|
attr.connect(onSignal)
|
|
|
|
|
|
def onRoomInvited(self, client: Client, room_id: str) -> None:
|
|
pass # TODO
|
|
|
|
|
|
def onRoomJoined(self, client: Client, room_id: str) -> None:
|
|
model = self.backend.models.rooms[client.userID]
|
|
room = client.nio.rooms[room_id]
|
|
|
|
def group_name() -> Optional[str]:
|
|
name = room.group_name()
|
|
return None if name == "Empty room?" else name
|
|
|
|
item = Room(
|
|
room_id = room_id,
|
|
display_name = room.name or room.canonical_alias or group_name(),
|
|
description = room.topic,
|
|
)
|
|
|
|
try:
|
|
index = model.indexWhere("room_id", room_id)
|
|
except ValueError:
|
|
model.append(item)
|
|
else:
|
|
model[index] = item
|
|
|
|
|
|
def onRoomLeft(self, client: Client, room_id: str) -> None:
|
|
rooms = self.backend.models.rooms[client.userID]
|
|
del rooms[rooms.indexWhere("room_id", room_id)]
|
|
|
|
|
|
def onRoomSyncPrevBatchTokenReceived(
|
|
self, _: Client, room_id: str, token: str
|
|
) -> None:
|
|
|
|
if room_id not in self.backend.past_tokens:
|
|
self.backend.past_tokens[room_id] = token
|
|
|
|
|
|
def onRoomPastPrevBatchTokenReceived(
|
|
self, _: Client, room_id: str, token: str
|
|
) -> None:
|
|
|
|
if self.backend.past_tokens[room_id] == token:
|
|
self.backend.fully_loaded_rooms.add(room_id)
|
|
|
|
self.backend.past_tokens[room_id] = token
|
|
|
|
|
|
def onRoomEventReceived(
|
|
self, _: Client, room_id: str, etype: str, edict: Dict[str, Any]
|
|
) -> None:
|
|
|
|
with self._event_handling_lock:
|
|
# Prevent duplicate events in models due to multiple accounts
|
|
if edict["event_id"] in self.last_room_events:
|
|
return
|
|
|
|
self.last_room_events.appendleft(edict["event_id"])
|
|
|
|
model = self.backend.models.roomEvents[room_id]
|
|
date_time = QDateTime\
|
|
.fromMSecsSinceEpoch(edict["server_timestamp"])
|
|
new_event = RoomEvent(type=etype, date_time=date_time, dict=edict)
|
|
|
|
if self._events_in_transfer:
|
|
local_echoes_met: int = 0
|
|
replace_at: Optional[int] = None
|
|
|
|
# Find if any locally echoed event corresponds to new_event
|
|
for i, event in enumerate(model):
|
|
if not event.is_local_echo:
|
|
continue
|
|
|
|
sb = (event.dict["sender"], event.dict["body"])
|
|
new_sb = (new_event.dict["sender"], new_event.dict["body"])
|
|
|
|
if sb == new_sb:
|
|
# The oldest matching local echo shall be replaced
|
|
replace_at = max(replace_at or 0, i)
|
|
|
|
local_echoes_met += 1
|
|
if local_echoes_met >= self._events_in_transfer:
|
|
break
|
|
|
|
if replace_at is not None:
|
|
model[replace_at] = new_event
|
|
self._events_in_transfer -= 1
|
|
return
|
|
|
|
for i, event in enumerate(model):
|
|
if event.is_local_echo:
|
|
continue
|
|
|
|
# Model is sorted from newest to oldest message
|
|
if new_event.date_time > event.date_time:
|
|
model.insert(i, new_event)
|
|
return
|
|
|
|
model.append(new_event)
|
|
|
|
|
|
def onRoomTypingUsersUpdated(
|
|
self, client: Client, room_id: str, users: List[str]
|
|
) -> None:
|
|
|
|
rooms = self.backend.models.rooms[client.userID]
|
|
rooms[rooms.indexWhere("room_id", room_id)].typing_users = users
|
|
|
|
|
|
def onMessageAboutToBeSent(
|
|
self, client: Client, room_id: str, content: Dict[str, str]
|
|
) -> None:
|
|
with self._event_handling_lock:
|
|
timestamp = QDateTime.currentMSecsSinceEpoch()
|
|
model = self.backend.models.roomEvents[room_id]
|
|
nio_event = nio.events.RoomMessage.parse_event({
|
|
"event_id": "",
|
|
"sender": client.userID,
|
|
"origin_server_ts": timestamp,
|
|
"content": content,
|
|
})
|
|
event = RoomEvent(
|
|
type = type(nio_event).__name__,
|
|
date_time = QDateTime.fromMSecsSinceEpoch(timestamp),
|
|
dict = nio_event.__dict__,
|
|
is_local_echo = True,
|
|
)
|
|
model.insert(0, event)
|
|
self._events_in_transfer += 1
|