Mercurial > libervia-backend
comparison 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 |
comparison
equal
deleted
inserted
replaced
1051:854880a31717 | 1052:e88bff4c8b77 |
---|---|
50 | 50 |
51 def sat_next_id(): | 51 def sat_next_id(): |
52 global sat_id | 52 global sat_id |
53 sat_id += 1 | 53 sat_id += 1 |
54 return "sat_id_" + str(sat_id) | 54 return "sat_id_" + str(sat_id) |
55 | |
56 | |
57 class MessageSentAndStored(Exception): | |
58 """ Exception to raise if the message has been already sent and stored in the | |
59 history by the trigger, so the rest of the process should be stopped. This | |
60 should normally be raised by the trigger with the minimal priority """ | |
61 def __init__(self, reason, mess_data): | |
62 Exception.__init__(self, reason) | |
63 self.mess_data = mess_data # added for testing purpose | |
64 | |
65 | |
66 class AbortSendMessage(Exception): | |
67 """ Exception to raise if sending the message should be aborted. This can be | |
68 raised by any trigger but a side action should be planned by the trigger | |
69 to inform the user about what happened """ | |
70 pass | |
71 | 55 |
72 | 56 |
73 class SAT(service.Service): | 57 class SAT(service.Service): |
74 | 58 |
75 @property | 59 @property |
431 if profile not in self.profiles: | 415 if profile not in self.profiles: |
432 return False | 416 return False |
433 return self.profiles[profile].isConnected() | 417 return self.profiles[profile].isConnected() |
434 | 418 |
435 | 419 |
436 ## jabber methods ## | 420 ## XMPP methods ## |
437 | 421 |
438 def getWaitingConf(self, profile_key=None): | 422 def getWaitingConf(self, profile_key=None): |
439 assert(profile_key) | 423 assert(profile_key) |
440 client = self.getClient(profile_key) | 424 client = self.getClient(profile_key) |
441 ret = [] | 425 ret = [] |
442 for conf_id in client._waiting_conf: | 426 for conf_id in client._waiting_conf: |
443 conf_type, data = client._waiting_conf[conf_id][:2] | 427 conf_type, data = client._waiting_conf[conf_id][:2] |
444 ret.append((conf_id, conf_type, data)) | 428 ret.append((conf_id, conf_type, data)) |
445 return ret | 429 return ret |
446 | 430 |
431 def generateMessageXML(self, mess_data): | |
432 mess_data['xml'] = domish.Element((None, 'message')) | |
433 mess_data['xml']["to"] = mess_data["to"].full() | |
434 mess_data['xml']["from"] = mess_data['from'].full() | |
435 mess_data['xml']["type"] = mess_data["type"] | |
436 mess_data['xml']['id'] = str(uuid4()) | |
437 if mess_data["subject"]: | |
438 mess_data['xml'].addElement("subject", None, mess_data['subject']) | |
439 if mess_data["message"]: # message without body are used to send chat states | |
440 mess_data['xml'].addElement("body", None, mess_data["message"]) | |
441 return mess_data | |
442 | |
447 def _sendMessage(self, to_s, msg, subject=None, mess_type='auto', extra={}, profile_key=C.PROF_KEY_NONE): | 443 def _sendMessage(self, to_s, msg, subject=None, mess_type='auto', extra={}, profile_key=C.PROF_KEY_NONE): |
448 to_jid = jid.JID(to_s) | 444 to_jid = jid.JID(to_s) |
449 #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 | 445 #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 |
450 return self.sendMessage(to_jid, msg, subject, mess_type, {unicode(key): unicode(value) for key, value in extra.items()}, profile_key=profile_key) | 446 return self.sendMessage(to_jid, msg, subject, mess_type, {unicode(key): unicode(value) for key, value in extra.items()}, profile_key=profile_key) |
451 | 447 |
452 def sendMessage(self, to_jid, msg, subject=None, mess_type='auto', extra={}, no_trigger=False, profile_key=C.PROF_KEY_NONE): | 448 def sendMessage(self, to_jid, msg, subject=None, mess_type='auto', extra={}, no_trigger=False, profile_key=C.PROF_KEY_NONE): |
453 #FIXME: check validity of recipient | 449 #FIXME: check validity of recipient |
454 profile = self.memory.getProfileName(profile_key) | 450 profile = self.memory.getProfileName(profile_key) |
455 assert(profile) | 451 assert(profile) |
456 client = self.profiles[profile] | 452 client = self.profiles[profile] |
457 current_jid = client.jid | |
458 if extra is None: | 453 if extra is None: |
459 extra = {} | 454 extra = {} |
460 mess_data = { # we put data in a dict, so trigger methods can change them | 455 mess_data = { # we put data in a dict, so trigger methods can change them |
461 "to": to_jid, | 456 "to": to_jid, |
457 "from": client.jid, | |
462 "message": msg, | 458 "message": msg, |
463 "subject": subject, | 459 "subject": subject, |
464 "type": mess_type, | 460 "type": mess_type, |
465 "extra": extra, | 461 "extra": extra, |
466 } | 462 } |
491 if not self.trigger.point("sendMessage", mess_data, pre_xml_treatments, post_xml_treatments, profile): | 487 if not self.trigger.point("sendMessage", mess_data, pre_xml_treatments, post_xml_treatments, profile): |
492 return defer.succeed(None) | 488 return defer.succeed(None) |
493 | 489 |
494 log.debug(_("Sending jabber message of type [%(type)s] to %(to)s...") % {"type": mess_data["type"], "to": to_jid.full()}) | 490 log.debug(_("Sending jabber message of type [%(type)s] to %(to)s...") % {"type": mess_data["type"], "to": to_jid.full()}) |
495 | 491 |
496 def generateXML(mess_data): | 492 def cancelErrorTrap(failure): |
497 mess_data['xml'] = domish.Element((None, 'message')) | 493 """A message sending can be cancelled by a plugin treatment""" |
498 mess_data['xml']["to"] = mess_data["to"].full() | 494 failure.trap(exceptions.CancelError) |
499 mess_data['xml']["from"] = current_jid.full() | 495 |
500 mess_data['xml']["type"] = mess_data["type"] | 496 pre_xml_treatments.addCallback(lambda dummy: self.generateMessageXML(mess_data)) |
501 if mess_data["subject"]: | |
502 mess_data['xml'].addElement("subject", None, subject) | |
503 # message without body are used to send chat states | |
504 if mess_data["message"]: | |
505 mess_data['xml'].addElement("body", None, mess_data["message"]) | |
506 return mess_data | |
507 | |
508 def sendErrback(e): | |
509 text = '%s: %s' % (e.value.__class__.__name__, e.getErrorMessage()) | |
510 if e.check(MessageSentAndStored): | |
511 log.debug(text) | |
512 elif e.check(AbortSendMessage): | |
513 log.warning(text) | |
514 return e | |
515 else: | |
516 log.error("Unmanaged exception: %s" % text) | |
517 return e | |
518 pre_xml_treatments.addCallback(generateXML) | |
519 pre_xml_treatments.chainDeferred(post_xml_treatments) | 497 pre_xml_treatments.chainDeferred(post_xml_treatments) |
520 post_xml_treatments.addCallback(self.sendAndStoreMessage, False, profile) | 498 post_xml_treatments.addCallback(self._sendMessageToStream, client) |
521 post_xml_treatments.addErrback(sendErrback) | 499 post_xml_treatments.addCallback(self._storeMessage, client) |
500 post_xml_treatments.addCallback(self.sendMessageToBridge, client) | |
501 post_xml_treatments.addErrback(cancelErrorTrap) | |
522 pre_xml_treatments.callback(mess_data) | 502 pre_xml_treatments.callback(mess_data) |
523 return pre_xml_treatments | 503 return pre_xml_treatments |
524 | 504 |
525 def sendAndStoreMessage(self, mess_data, skip_send=False, profile=None): | 505 def _sendMessageToStream(self, mess_data, client): |
526 """Actually send and store the message to history, after all the treatments have been done. | 506 """Actualy send the message to the server |
527 This has been moved outside the main sendMessage method because it is used by XEP-0033 to complete a server-side feature not yet | 507 |
528 implemented by the prosody plugin. | 508 @param mess_data: message data dictionnary |
529 @param mess_data: message data dictionary | 509 @param client: profile's client |
530 @param skip_send: set to True to skip sending the message to only store it | 510 """ |
531 @param profile: profile | 511 client.xmlstream.send(mess_data['xml']) |
532 """ | 512 return mess_data |
533 try: | 513 |
534 client = self.profiles[profile] | 514 def _storeMessage(self, mess_data, client): |
535 except KeyError: | 515 """Store message into database (for local history) |
536 log.error(_("Trying to send a message with no profile")) | 516 |
537 return | 517 @param mess_data: message data dictionnary |
538 current_jid = client.jid | 518 @param client: profile's client |
539 if not skip_send: | 519 """ |
540 client.xmlstream.send(mess_data['xml']) | |
541 if mess_data["type"] != "groupchat": | 520 if mess_data["type"] != "groupchat": |
542 # we don't add groupchat message to history, as we get them back | 521 # we don't add groupchat message to history, as we get them back |
543 # and they will be added then | 522 # and they will be added then |
544 if mess_data['message']: # we need a message to save something | 523 if mess_data['message']: # we need a message to save something |
545 self.memory.addToHistory(current_jid, mess_data['to'], | 524 self.memory.addToHistory(client.jid, mess_data['to'], |
546 unicode(mess_data["message"]), | 525 unicode(mess_data["message"]), |
547 unicode(mess_data["type"]), | 526 unicode(mess_data["type"]), |
548 mess_data['extra'], | 527 mess_data['extra'], |
549 profile=profile) | 528 profile=client.profile) |
529 else: | |
530 log.warning(_("No message found")) # empty body should be managed by plugins before this point | |
531 return mess_data | |
532 | |
533 def sendMessageToBridge(self, mess_data, client): | |
534 """Send message to bridge, so frontends can display it | |
535 | |
536 @param mess_data: message data dictionnary | |
537 @param client: profile's client | |
538 """ | |
539 if mess_data["type"] != "groupchat": | |
540 # we don't send groupchat message back to bridge, as we get them back | |
541 # and they will be added the | |
542 if mess_data['message']: # we need a message to save something | |
550 # We send back the message, so all clients are aware of it | 543 # We send back the message, so all clients are aware of it |
551 self.bridge.newMessage(mess_data['xml']['from'], | 544 self.bridge.newMessage(mess_data['from'].full(), |
552 unicode(mess_data["message"]), | 545 unicode(mess_data["message"]), |
553 mess_type=mess_data["type"], | 546 mess_type=mess_data["type"], |
554 to_jid=mess_data['xml']['to'], | 547 to_jid=mess_data['to'].full(), |
555 extra=mess_data['extra'], | 548 extra=mess_data['extra'], |
556 profile=profile) | 549 profile=client.profile) |
550 else: | |
551 log.warning(_("No message found")) | |
552 return mess_data | |
557 | 553 |
558 def _setPresence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE): | 554 def _setPresence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE): |
559 return self.setPresence(jid.JID(to) if to else None, show, statuses, profile_key) | 555 return self.setPresence(jid.JID(to) if to else None, show, statuses, profile_key) |
560 | 556 |
561 def setPresence(self, to_jid=None, show="", statuses=None, profile_key=C.PROF_KEY_NONE): | 557 def setPresence(self, to_jid=None, show="", statuses=None, profile_key=C.PROF_KEY_NONE): |