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
This commit is contained in:
Goffi 2014-06-07 16:35:29 +02:00
commit 87b3e413ee

View file

@ -54,22 +54,6 @@ def sat_next_id():
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 @@ class SAT(service.Service):
return self.profiles[profile].isConnected()
## jabber methods ##
## XMPP methods ##
def getWaitingConf(self, profile_key=None):
assert(profile_key)
@ -444,6 +428,18 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
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 @@ class SAT(service.Service):
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
"""
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
"""
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'])
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)