Add local echoing of messages

As per
https://matrix.org/docs/spec/client_server/latest.html#local-echo
This commit is contained in:
miruka 2019-04-18 13:46:39 -04:00
parent e9b3628fcc
commit 11d900965a
5 changed files with 85 additions and 22 deletions

View File

@ -36,3 +36,6 @@
- nio: org.matrix.room.preview\_urls, m.room.aliases - nio: org.matrix.room.preview\_urls, m.room.aliases
- Markdown: don't turn #things into title (space), disable __ syntax - 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

View File

@ -46,6 +46,7 @@ class Client(QObject):
roomPastPrevBatchTokenReceived = pyqtSignal(str, str) roomPastPrevBatchTokenReceived = pyqtSignal(str, str)
roomEventReceived = pyqtSignal(str, str, dict) roomEventReceived = pyqtSignal(str, str, dict)
roomTypingUsersUpdated = pyqtSignal(str, list) roomTypingUsersUpdated = pyqtSignal(str, list)
messageAboutToBeSent = pyqtSignal(str, dict)
def __init__(self, def __init__(self,
@ -171,7 +172,6 @@ class Client(QObject):
return return
self._loading = True self._loading = True
print("load", limit)
self._on_past_events( self._on_past_events(
room_id, room_id,
self.net.talk( self.net.talk(
@ -201,4 +201,11 @@ class Client(QObject):
"format": "org.matrix.custom.html", "format": "org.matrix.custom.html",
"msgtype": "m.text", "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,
)

View File

@ -21,6 +21,7 @@ class Room(NamedTuple):
class RoomEvent(NamedTuple): class RoomEvent(NamedTuple):
type: str type: str
date_time: QDateTime date_time: QDateTime
dict: Dict[str, str] dict: Dict[str, str]
is_local_echo: bool = False

View File

@ -6,19 +6,22 @@ from typing import Any, Deque, Dict, List, Optional
from PyQt5.QtCore import QDateTime, QObject, pyqtBoundSignal from PyQt5.QtCore import QDateTime, QObject, pyqtBoundSignal
import nio
from .backend import Backend from .backend import Backend
from .client import Client from .client import Client
from .model.items import Room, RoomEvent, User from .model.items import Room, RoomEvent, User
class SignalManager(QObject): class SignalManager(QObject):
_duplicate_check_lock: Lock = Lock() _event_handling_lock: Lock = Lock()
def __init__(self, backend: Backend) -> None: def __init__(self, backend: Backend) -> None:
super().__init__(parent=backend) super().__init__(parent=backend)
self.backend = backend self.backend = backend
self.last_room_events: Deque[str] = Deque(maxlen=1000) self.last_room_events: Deque[str] = Deque(maxlen=1000)
self._events_in_transfer: int = 0
cm = self.backend.clientManager cm = self.backend.clientManager
cm.clientAdded.connect(self.onClientAdded) cm.clientAdded.connect(self.onClientAdded)
@ -103,28 +106,53 @@ class SignalManager(QObject):
self, _: Client, room_id: str, etype: str, edict: Dict[str, Any] self, _: Client, room_id: str, etype: str, edict: Dict[str, Any]
) -> None: ) -> None:
# Prevent duplicate events in models due to multiple accounts with self._event_handling_lock:
with self._duplicate_check_lock: # Prevent duplicate events in models due to multiple accounts
if edict["event_id"] in self.last_room_events: if edict["event_id"] in self.last_room_events:
return return
self.last_room_events.appendleft(edict["event_id"]) self.last_room_events.appendleft(edict["event_id"])
model = self.backend.models.roomEvents[room_id] model = self.backend.models.roomEvents[room_id]
date_time = QDateTime.fromMSecsSinceEpoch(edict["server_timestamp"]) date_time = QDateTime\
new_event = RoomEvent(type=etype, date_time=date_time, dict=edict) .fromMSecsSinceEpoch(edict["server_timestamp"])
new_event = RoomEvent(type=etype, date_time=date_time, dict=edict)
# Model is sorted from newest to oldest message if self._events_in_transfer:
insert_at = None local_echoes_met: int = 0
for i, event in enumerate(model): replace_at: Optional[int] = None
if new_event.date_time > event.date_time:
insert_at = i # Find if any locally echoed event corresponds to new_event
break 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) model.append(new_event)
else:
model.insert(insert_at, new_event)
def onRoomTypingUsersUpdated( def onRoomTypingUsersUpdated(
@ -133,3 +161,25 @@ class SignalManager(QObject):
rooms = self.backend.models.rooms[client.userID] rooms = self.backend.models.rooms[client.userID]
rooms[rooms.indexWhere("room_id", room_id)].typing_users = users 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

View File

@ -37,13 +37,15 @@ Row {
//Qt.formatDateTime(date_time, "hh:mm:ss") + //Qt.formatDateTime(date_time, "hh:mm:ss") +
//"</font>" + //"</font>" +
// (isOwn ? "&nbsp;&nbsp;" + content : "") // (isOwn ? "&nbsp;&nbsp;" + content : "")
//
text: (dict.formatted_body ? text: (dict.formatted_body ?
Backend.htmlFilter.filter(dict.formatted_body) : Backend.htmlFilter.filter(dict.formatted_body) :
dict.body) + dict.body) +
"&nbsp;&nbsp;<font size=" + smallSize + "px color=gray>" + "&nbsp;&nbsp;<font size=" + smallSize + "px color=gray>" +
Qt.formatDateTime(date_time, "hh:mm:ss") + Qt.formatDateTime(date_time, "hh:mm:ss") +
"</font>" "</font>" +
(is_local_echo ?
"&nbsp;<font size=" + smallSize + "px>⏳</font>" : "")
textFormat: Text.RichText textFormat: Text.RichText
background: Rectangle {color: "#DDD"} background: Rectangle {color: "#DDD"}
wrapMode: Text.Wrap wrapMode: Text.Wrap