changeset 3060:730bbed77a89

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
author Goffi <goffi@goffi.org>
date Fri, 18 Oct 2019 14:45:07 +0200 (2019-10-18)
parents aa728dc7b0ce
children 948833e3b542
files sat/plugins/plugin_xep_0045.py
diffstat 1 files changed, 73 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/sat/plugins/plugin_xep_0045.py	Fri Oct 18 14:36:46 2019 +0200
+++ b/sat/plugins/plugin_xep_0045.py	Fri Oct 18 14:45:07 2019 +0200
@@ -86,24 +86,61 @@
         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 @@
             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 @@
             # 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 @@
             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 @@
                             '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 @@
         # 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 @@
         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: