From 11d900965abf406adc4d6ed5375b2e84eef5c426 Mon Sep 17 00:00:00 2001 From: miruka Date: Thu, 18 Apr 2019 13:46:39 -0400 Subject: [PATCH] Add local echoing of messages As per https://matrix.org/docs/spec/client_server/latest.html#local-echo --- TODO.md | 3 + harmonyqml/backend/client.py | 11 ++- harmonyqml/backend/model/items.py | 7 +- harmonyqml/backend/signal_manager.py | 80 +++++++++++++++---- harmonyqml/components/chat/MessageContent.qml | 6 +- 5 files changed, 85 insertions(+), 22 deletions(-) diff --git a/TODO.md b/TODO.md index c633866a..b2338fef 100644 --- a/TODO.md +++ b/TODO.md @@ -36,3 +36,6 @@ - nio: org.matrix.room.preview\_urls, m.room.aliases - Markdown: don't turn #things into title (space), disable __ syntax +- ![A picture](https://picsum.photos/256/256) not clickable? + +- On sync, check messages API, if a limited sync timeline was received diff --git a/harmonyqml/backend/client.py b/harmonyqml/backend/client.py index 39665d27..6ceb38a2 100644 --- a/harmonyqml/backend/client.py +++ b/harmonyqml/backend/client.py @@ -46,6 +46,7 @@ class Client(QObject): roomPastPrevBatchTokenReceived = pyqtSignal(str, str) roomEventReceived = pyqtSignal(str, str, dict) roomTypingUsersUpdated = pyqtSignal(str, list) + messageAboutToBeSent = pyqtSignal(str, dict) def __init__(self, @@ -171,7 +172,6 @@ class Client(QObject): return self._loading = True - print("load", limit) self._on_past_events( room_id, self.net.talk( @@ -201,4 +201,11 @@ class Client(QObject): "format": "org.matrix.custom.html", "msgtype": "m.text", } - self.net.talk(self.nio.room_send, room_id, "m.room.message", content) + self.messageAboutToBeSent.emit(room_id, content) + + self.net.talk( + self.nio.room_send, + room_id = room_id, + message_type = "m.room.message", + content = content, + ) diff --git a/harmonyqml/backend/model/items.py b/harmonyqml/backend/model/items.py index e785f504..37134d9c 100644 --- a/harmonyqml/backend/model/items.py +++ b/harmonyqml/backend/model/items.py @@ -21,6 +21,7 @@ class Room(NamedTuple): class RoomEvent(NamedTuple): - type: str - date_time: QDateTime - dict: Dict[str, str] + type: str + date_time: QDateTime + dict: Dict[str, str] + is_local_echo: bool = False diff --git a/harmonyqml/backend/signal_manager.py b/harmonyqml/backend/signal_manager.py index 54c44ab6..3aeb24ec 100644 --- a/harmonyqml/backend/signal_manager.py +++ b/harmonyqml/backend/signal_manager.py @@ -6,19 +6,22 @@ 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): - _duplicate_check_lock: Lock = Lock() + _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) @@ -103,28 +106,53 @@ class SignalManager(QObject): self, _: Client, room_id: str, etype: str, edict: Dict[str, Any] ) -> None: - # Prevent duplicate events in models due to multiple accounts - with self._duplicate_check_lock: + 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) + 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) - # Model is sorted from newest to oldest message - insert_at = None - for i, event in enumerate(model): - if new_event.date_time > event.date_time: - insert_at = i - break + 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 - if insert_at is None: model.append(new_event) - else: - model.insert(insert_at, new_event) def onRoomTypingUsersUpdated( @@ -133,3 +161,25 @@ class SignalManager(QObject): 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 diff --git a/harmonyqml/components/chat/MessageContent.qml b/harmonyqml/components/chat/MessageContent.qml index 305f9f12..8adad2ef 100644 --- a/harmonyqml/components/chat/MessageContent.qml +++ b/harmonyqml/components/chat/MessageContent.qml @@ -37,13 +37,15 @@ Row { //Qt.formatDateTime(date_time, "hh:mm:ss") + //"" + // (isOwn ? "  " + content : "") - + // text: (dict.formatted_body ? Backend.htmlFilter.filter(dict.formatted_body) : dict.body) + "  " + Qt.formatDateTime(date_time, "hh:mm:ss") + - "" + "" + + (is_local_echo ? + " " : "") textFormat: Text.RichText background: Rectangle {color: "#DDD"} wrapMode: Text.Wrap