plugin XEP-0045: join / MAM history improvements:

- fixed use of history_d deferred so MAM history is stored in database as soon as possible
  and in right order
- get MAM history with pages of 20 messages instead of 100
- if a message can't be parsed, an error message is logged (with a dump of the stanza),
  and the message is ignored, avoiding a crash and the impossibility the join the room at
  all
- a new `mucRoomPrepareJoin` signal, containing room jid and profile, is sent when a room
  joining is started, this allow better UX as the frontend can show the room immediately,
  and lock it with a waiting message until it is fully available. The `mucRoomJoined` is
  still sent when the room is fully joined/avaiable, meaning that the frontend can unlock
  it and let the user interact with it
This commit is contained in:
Goffi 2019-10-18 14:45:07 +02:00
commit f065ba4ff6

View file

@ -86,24 +86,61 @@ class XEP_0045(object):
log.info(_("Plugin XEP_0045 initialization"))
self.host = host
self._sessions = memory.Sessions()
host.bridge.addMethod("mucJoin", ".plugin", in_sign='ssa{ss}s', out_sign='(bsa{sa{ss}}sss)', method=self._join, async_=True) # return same arguments as mucRoomJoined + a boolean set to True is the room was already joined (first argument)
host.bridge.addMethod("mucNick", ".plugin", in_sign='sss', out_sign='', method=self._nick)
host.bridge.addMethod("mucNickGet", ".plugin", in_sign='ss', out_sign='s', method=self._getRoomNick)
host.bridge.addMethod("mucLeave", ".plugin", in_sign='ss', out_sign='', method=self._leave, async_=True)
host.bridge.addMethod("mucOccupantsGet", ".plugin", in_sign='ss', out_sign='a{sa{ss}}', method=self._getRoomOccupants)
host.bridge.addMethod("mucSubject", ".plugin", in_sign='sss', out_sign='', method=self._subject)
host.bridge.addMethod("mucGetRoomsJoined", ".plugin", in_sign='s', out_sign='a(sa{sa{ss}}ss)', method=self._getRoomsJoined)
host.bridge.addMethod("mucGetUniqueRoomName", ".plugin", in_sign='ss', out_sign='s', method=self._getUniqueName)
host.bridge.addMethod("mucConfigureRoom", ".plugin", in_sign='ss', out_sign='s', method=self._configureRoom, async_=True)
host.bridge.addMethod("mucGetDefaultService", ".plugin", in_sign='', out_sign='s', method=self.getDefaultMUC)
host.bridge.addMethod("mucGetService", ".plugin", in_sign='ss', out_sign='s', method=self._getMUCService, async_=True)
host.bridge.addSignal("mucRoomJoined", ".plugin", signature='sa{sa{ss}}sss') # args: room_jid, occupants, user_nick, subject, profile
host.bridge.addSignal("mucRoomLeft", ".plugin", signature='ss') # args: room_jid, profile
host.bridge.addSignal("mucRoomUserChangedNick", ".plugin", signature='ssss') # args: room_jid, old_nick, new_nick, profile
host.bridge.addSignal("mucRoomNewSubject", ".plugin", signature='sss') # args: room_jid, subject, profile
self.__submit_conf_id = host.registerCallback(self._submitConfiguration, with_data=True)
# return same arguments as mucRoomJoined + a boolean set to True is the room was
# already joined (first argument)
host.bridge.addMethod(
"mucJoin", ".plugin", in_sign='ssa{ss}s', out_sign='(bsa{sa{ss}}sss)',
method=self._join, async_=True)
host.bridge.addMethod(
"mucNick", ".plugin", in_sign='sss', out_sign='', method=self._nick)
host.bridge.addMethod(
"mucNickGet", ".plugin", in_sign='ss', out_sign='s', method=self._getRoomNick)
host.bridge.addMethod(
"mucLeave", ".plugin", in_sign='ss', out_sign='', method=self._leave,
async_=True)
host.bridge.addMethod(
"mucOccupantsGet", ".plugin", in_sign='ss', out_sign='a{sa{ss}}',
method=self._getRoomOccupants)
host.bridge.addMethod(
"mucSubject", ".plugin", in_sign='sss', out_sign='', method=self._subject)
host.bridge.addMethod(
"mucGetRoomsJoined", ".plugin", in_sign='s', out_sign='a(sa{sa{ss}}ss)',
method=self._getRoomsJoined)
host.bridge.addMethod(
"mucGetUniqueRoomName", ".plugin", in_sign='ss', out_sign='s',
method=self._getUniqueName)
host.bridge.addMethod(
"mucConfigureRoom", ".plugin", in_sign='ss', out_sign='s',
method=self._configureRoom, async_=True)
host.bridge.addMethod(
"mucGetDefaultService", ".plugin", in_sign='', out_sign='s',
method=self.getDefaultMUC)
host.bridge.addMethod(
"mucGetService", ".plugin", in_sign='ss', out_sign='s',
method=self._getMUCService, async_=True)
# called when a room will be joined but must be locked until join is received
# (room is prepared, history is getting retrieved)
# args: room_jid, profile
host.bridge.addSignal(
"mucRoomPrepareJoin", ".plugin", signature='ss')
# args: room_jid, occupants, user_nick, subject, profile
host.bridge.addSignal(
"mucRoomJoined", ".plugin", signature='sa{sa{ss}}sss')
# args: room_jid, profile
host.bridge.addSignal(
"mucRoomLeft", ".plugin", signature='ss')
# args: room_jid, old_nick, new_nick, profile
host.bridge.addSignal(
"mucRoomUserChangedNick", ".plugin", signature='ssss')
# args: room_jid, subject, profile
host.bridge.addSignal(
"mucRoomNewSubject", ".plugin", signature='sss')
self.__submit_conf_id = host.registerCallback(
self._submitConfiguration, with_data=True)
self._room_join_id = host.registerCallback(self._UIRoomJoinCb, with_data=True)
host.importMenu((D_("MUC"), D_("configure")), self._configureRoomMenu, security_limit=0, help_string=D_("Configure Multi-User Chat room"), type_=C.MENU_ROOM)
host.importMenu(
(D_("MUC"), D_("configure")), self._configureRoomMenu, security_limit=0,
help_string=D_("Configure Multi-User Chat room"), type_=C.MENU_ROOM)
try:
self.text_cmds = self.host.plugins[C.TEXT_CMDS]
except KeyError:
@ -487,6 +524,7 @@ class XEP_0045(object):
return defer.fail(AlreadyJoined(room))
log.info(_("[{profile}] is joining room {room} with nick {nick}").format(
profile=client.profile, room=room_jid.userhost(), nick=nick))
self.host.bridge.mucRoomPrepareJoin(room_jid.userhost(), client.profile)
password = options.get("password")
@ -911,7 +949,10 @@ class SatMUCClient(muc.MUCClient):
# we don't want any history from room as we'll get it with MAM
room_jid, nick, muc.HistoryOptions(maxStanzas=0), password=password)
room._history_type = HISTORY_MAM
room._history_d = defer.Deferred()
history_d = room._history_d = defer.Deferred()
# we trigger now the deferred so all callback are processed as soon as possible
# and in order
history_d.callback(None)
last_mess = yield self.host.memory.historyGet(
room_jid,
@ -924,7 +965,7 @@ class SatMUCClient(muc.MUCClient):
profile=client.profile)
if last_mess:
stanza_id = last_mess[0][-1]['stanza_id']
rsm_req = rsm.RSMRequest(max_=100, after=stanza_id)
rsm_req = rsm.RSMRequest(max_=20, after=stanza_id)
no_loop=False
else:
log.info("We have no MAM archive for room {room_jid}.".format(
@ -961,7 +1002,14 @@ class SatMUCClient(muc.MUCClient):
'Forwarded message element has a "to" attribute while it is '
'forbidden by specifications')
fwd_message_elt["to"] = client.jid.full()
mess_data = client.messageProt.parseMessage(fwd_message_elt)
try:
mess_data = client.messageProt.parseMessage(fwd_message_elt)
except Exception as e:
log.error(
f"Can't parse message, ignoring it: {e}\n"
f"{fwd_message_elt.toXml()}"
)
continue
# we attache parsed message data to element, to avoid parsing
# again in _addToHistory
fwd_message_elt._mess_data = mess_data
@ -981,11 +1029,11 @@ class SatMUCClient(muc.MUCClient):
# the order is different (we have to join then get MAM archive, so subject
# is received before archive), so we change state and add the callbacks here.
self.changeRoomState(room, ROOM_STATE_LIVE)
room._history_d.addCallbacks(self._historyCb, self._historyEb, [room],
history_d.addCallbacks(self._historyCb, self._historyEb, [room],
errbackArgs=[room])
# callback is done now that all needed Deferred have been added to _history_d
room._history_d.callback(None)
# we wait for all callbacks to be processed
yield history_d
defer.returnValue(room)
@ -1243,8 +1291,8 @@ class SatMUCClient(muc.MUCClient):
There are a few event methods that may get called here.
L{receivedGroupChat}, L{receivedSubject} or L{receivedHistory}.
"""
# We override this method to fix subject handling
# FIXME: remove this merge fixed upstream
# We override this method to fix subject handling (empty strings were discarded)
# FIXME: remove this once fixed upstream
room, user = self._getRoomUser(message)
if room is None: