# 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 = client.userID.lstrip("@").split(":")[0], )) 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 = getattr(room, "topic", ""), # FIXME: outside init ) 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