diff src/core/sat_main.py @ 1052:e88bff4c8b77

core (XMPP): sendMessage refactoring: - better separation of message sending actions - use of more generic exceptions to hook the behaviour (SkipHistory and CancelError) - use of raise instead of return - use of failure.trap
author Goffi <goffi@goffi.org>
date Sat, 07 Jun 2014 16:35:29 +0200
parents 066308706dc6
children b2b9c184033f
line wrap: on
line diff
--- a/src/core/sat_main.py	Tue Jun 03 17:10:12 2014 +0200
+++ b/src/core/sat_main.py	Sat Jun 07 16:35:29 2014 +0200
@@ -54,22 +54,6 @@
     return "sat_id_" + str(sat_id)
 
 
-class MessageSentAndStored(Exception):
-    """ Exception to raise if the message has been already sent and stored in the
-    history by the trigger, so the rest of the process should be stopped. This
-    should normally be raised by the trigger with the minimal priority """
-    def __init__(self, reason, mess_data):
-        Exception.__init__(self, reason)
-        self.mess_data = mess_data  # added for testing purpose
-
-
-class AbortSendMessage(Exception):
-    """ Exception to raise if sending the message should be aborted. This can be
-    raised by any trigger but a side action should be planned by the trigger
-    to inform the user about what happened """
-    pass
-
-
 class SAT(service.Service):
 
     @property
@@ -433,7 +417,7 @@
         return self.profiles[profile].isConnected()
 
 
-    ## jabber methods ##
+    ## XMPP methods ##
 
     def getWaitingConf(self, profile_key=None):
         assert(profile_key)
@@ -444,6 +428,18 @@
             ret.append((conf_id, conf_type, data))
         return ret
 
+    def generateMessageXML(self, mess_data):
+        mess_data['xml'] = domish.Element((None, 'message'))
+        mess_data['xml']["to"] = mess_data["to"].full()
+        mess_data['xml']["from"] = mess_data['from'].full()
+        mess_data['xml']["type"] = mess_data["type"]
+        mess_data['xml']['id'] = str(uuid4())
+        if mess_data["subject"]:
+            mess_data['xml'].addElement("subject", None, mess_data['subject'])
+        if mess_data["message"]: # message without body are used to send chat states
+            mess_data['xml'].addElement("body", None, mess_data["message"])
+        return mess_data
+
     def _sendMessage(self, to_s, msg, subject=None, mess_type='auto', extra={}, profile_key=C.PROF_KEY_NONE):
         to_jid = jid.JID(to_s)
         #XXX: we need to use the dictionary comprehension because D-Bus return its own types, and pickle can't manage them. TODO: Need to find a better way
@@ -454,11 +450,11 @@
         profile = self.memory.getProfileName(profile_key)
         assert(profile)
         client = self.profiles[profile]
-        current_jid = client.jid
         if extra is None:
             extra = {}
         mess_data = {  # we put data in a dict, so trigger methods can change them
             "to": to_jid,
+            "from": client.jid,
             "message": msg,
             "subject": subject,
             "type": mess_type,
@@ -493,67 +489,67 @@
 
         log.debug(_("Sending jabber message of type [%(type)s] to %(to)s...") % {"type": mess_data["type"], "to": to_jid.full()})
 
-        def generateXML(mess_data):
-            mess_data['xml'] = domish.Element((None, 'message'))
-            mess_data['xml']["to"] = mess_data["to"].full()
-            mess_data['xml']["from"] = current_jid.full()
-            mess_data['xml']["type"] = mess_data["type"]
-            if mess_data["subject"]:
-                mess_data['xml'].addElement("subject", None, subject)
-            # message without body are used to send chat states
-            if mess_data["message"]:
-                mess_data['xml'].addElement("body", None, mess_data["message"])
-            return mess_data
+        def cancelErrorTrap(failure):
+            """A message sending can be cancelled by a plugin treatment"""
+            failure.trap(exceptions.CancelError)
 
-        def sendErrback(e):
-            text = '%s: %s' % (e.value.__class__.__name__, e.getErrorMessage())
-            if e.check(MessageSentAndStored):
-                log.debug(text)
-            elif e.check(AbortSendMessage):
-                log.warning(text)
-                return e
-            else:
-                log.error("Unmanaged exception: %s" % text)
-                return e
-        pre_xml_treatments.addCallback(generateXML)
+        pre_xml_treatments.addCallback(lambda dummy: self.generateMessageXML(mess_data))
         pre_xml_treatments.chainDeferred(post_xml_treatments)
-        post_xml_treatments.addCallback(self.sendAndStoreMessage, False, profile)
-        post_xml_treatments.addErrback(sendErrback)
+        post_xml_treatments.addCallback(self._sendMessageToStream, client)
+        post_xml_treatments.addCallback(self._storeMessage, client)
+        post_xml_treatments.addCallback(self.sendMessageToBridge, client)
+        post_xml_treatments.addErrback(cancelErrorTrap)
         pre_xml_treatments.callback(mess_data)
         return pre_xml_treatments
 
-    def sendAndStoreMessage(self, mess_data, skip_send=False, profile=None):
-        """Actually send and store the message to history, after all the treatments have been done.
-        This has been moved outside the main sendMessage method because it is used by XEP-0033 to complete a server-side feature not yet
-        implemented by the prosody plugin.
-        @param mess_data: message data dictionary
-        @param skip_send: set to True to skip sending the message to only store it
-        @param profile: profile
+    def _sendMessageToStream(self, mess_data, client):
+        """Actualy send the message to the server
+
+        @param mess_data: message data dictionnary
+        @param client: profile's client
         """
-        try:
-            client = self.profiles[profile]
-        except KeyError:
-            log.error(_("Trying to send a message with no profile"))
-            return
-        current_jid = client.jid
-        if not skip_send:
-            client.xmlstream.send(mess_data['xml'])
+        client.xmlstream.send(mess_data['xml'])
+        return mess_data
+
+    def _storeMessage(self, mess_data, client):
+        """Store message into database (for local history)
+
+        @param mess_data: message data dictionnary
+        @param client: profile's client
+        """
         if mess_data["type"] != "groupchat":
             # we don't add groupchat message to history, as we get them back
             # and they will be added then
             if mess_data['message']: # we need a message to save something
-                self.memory.addToHistory(current_jid, mess_data['to'],
+                self.memory.addToHistory(client.jid, mess_data['to'],
                                      unicode(mess_data["message"]),
                                      unicode(mess_data["type"]),
                                      mess_data['extra'],
-                                     profile=profile)
+                                     profile=client.profile)
+            else:
+               log.warning(_("No message found")) # empty body should be managed by plugins before this point
+        return mess_data
+
+    def sendMessageToBridge(self, mess_data, client):
+        """Send message to bridge, so frontends can display it
+
+        @param mess_data: message data dictionnary
+        @param client: profile's client
+        """
+        if mess_data["type"] != "groupchat":
+            # we don't send groupchat message back to bridge, as we get them back
+            # and they will be added the
+            if mess_data['message']: # we need a message to save something
                 # We send back the message, so all clients are aware of it
-                self.bridge.newMessage(mess_data['xml']['from'],
+                self.bridge.newMessage(mess_data['from'].full(),
                                        unicode(mess_data["message"]),
                                        mess_type=mess_data["type"],
-                                       to_jid=mess_data['xml']['to'],
+                                       to_jid=mess_data['to'].full(),
                                        extra=mess_data['extra'],
-                                       profile=profile)
+                                       profile=client.profile)
+            else:
+               log.warning(_("No message found"))
+        return mess_data
 
     def _setPresence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE):
         return self.setPresence(jid.JID(to) if to else None, show, statuses, profile_key)