Mercurial > libervia-backend
comparison src/core/sat_main.py @ 742:03744d9ebc13
plugin XEP-0033: implementation of the addressing feature:
- frontends pass the recipients in the extra parameter of sendMessage
- backend checks if the target server supports the feature (this is not done yet by prosody plugin)
- features and identities are cached per profile and server
- messages are duplicated in history for now (TODO: redesign the database)
- echos signals are also duplicated to the sender (FIXME)
author | souliane <souliane@mailoo.org> |
---|---|
date | Wed, 11 Dec 2013 17:16:53 +0100 |
parents | e07afabc4a25 |
children | 5aff0beddb28 |
comparison
equal
deleted
inserted
replaced
741:00318e60a06a | 742:03744d9ebc13 |
---|---|
58 | 58 |
59 def sat_next_id(): | 59 def sat_next_id(): |
60 global sat_id | 60 global sat_id |
61 sat_id += 1 | 61 sat_id += 1 |
62 return "sat_id_" + str(sat_id) | 62 return "sat_id_" + str(sat_id) |
63 | |
64 | |
65 class MessageSentAndStored(Exception): | |
66 """ Exception to raise if the message has been already sent and stored in the | |
67 history by the trigger, so the rest of the process should be stopped. This | |
68 should normally be raised by the trigger with the minimal priority """ | |
69 pass | |
70 | |
71 | |
72 class AbortSendMessage(Exception): | |
73 """ Exception to raise if sending the message should be aborted. This can be | |
74 raised by any trigger but a side action should be planned by the trigger | |
75 to inform the user about what happened """ | |
76 pass | |
63 | 77 |
64 | 78 |
65 class SAT(service.Service): | 79 class SAT(service.Service): |
66 | 80 |
67 def get_next_id(self): | 81 def get_next_id(self): |
352 return self.profiles.values() | 366 return self.profiles.values() |
353 if profile.count('@') > 1: | 367 if profile.count('@') > 1: |
354 raise exceptions.ProfileKeyUnknownError | 368 raise exceptions.ProfileKeyUnknownError |
355 return [self.profiles[profile]] | 369 return [self.profiles[profile]] |
356 | 370 |
371 def getClientHostJid(self, profile_key): | |
372 """Convenient method to get the client host from profile key | |
373 @return: host jid or None if it doesn't exist""" | |
374 profile = self.memory.getProfileName(profile_key) | |
375 if not profile: | |
376 return None | |
377 return self.profiles[profile].getHostJid() | |
378 | |
357 def registerNewAccount(self, login, password, email, server, port=5222, id=None, profile_key='@DEFAULT@'): | 379 def registerNewAccount(self, login, password, email, server, port=5222, id=None, profile_key='@DEFAULT@'): |
358 """Connect to a server and create a new account using in-band registration""" | 380 """Connect to a server and create a new account using in-band registration""" |
359 profile = self.memory.getProfileName(profile_key) | 381 profile = self.memory.getProfileName(profile_key) |
360 assert(profile) | 382 assert(profile) |
361 | 383 |
539 mess_data['xml'].addElement("subject", None, subject) | 561 mess_data['xml'].addElement("subject", None, subject) |
540 # message without body are used to send chat states | 562 # message without body are used to send chat states |
541 if mess_data["message"]: | 563 if mess_data["message"]: |
542 mess_data['xml'].addElement("body", None, mess_data["message"]) | 564 mess_data['xml'].addElement("body", None, mess_data["message"]) |
543 | 565 |
544 def sendAndStore(mess_data): | 566 def sendErrback(e): |
567 text = '%s: %s' % (e.value.__class__.__name__, e.getErrorMessage()) | |
568 if e.check(MessageSentAndStored): | |
569 debug(text) | |
570 elif e.check(AbortSendMessage): | |
571 warning(text) | |
572 else: | |
573 error("Unmanaged exception: %s" % text) | |
574 return e | |
575 | |
576 treatments.addCallbacks(self.sendAndStoreMessage, sendErrback, [False, profile]) | |
577 treatments.callback(mess_data) | |
578 | |
579 def sendAndStoreMessage(self, mess_data, skip_send=False, profile=None): | |
580 """Actually send and store the message to history, after all the treatments | |
581 have been done. This has been moved outside the main sendMessage method | |
582 because it is used by XEP-0033 to complete a server-side feature not yet | |
583 implemented by the prosody plugin. | |
584 @param mess_data: message data dictionary | |
585 @param skip_send: set to True to skip sending the message to only store it | |
586 @param profile: profile | |
587 """ | |
588 try: | |
589 client = self.profiles[profile] | |
590 except KeyError: | |
591 error(_("Trying to send a message with no profile")) | |
592 return | |
593 current_jid = client.jid | |
594 if not skip_send: | |
545 client.xmlstream.send(mess_data['xml']) | 595 client.xmlstream.send(mess_data['xml']) |
546 if mess_data["type"] != "groupchat": | 596 if mess_data["type"] != "groupchat": |
547 # we don't add groupchat message to history, as we get them back | 597 # we don't add groupchat message to history, as we get them back |
548 # and they will be added then | 598 # and they will be added then |
549 if mess_data['message']: # we need a message to save something | 599 if mess_data['message']: # we need a message to save something |
550 self.memory.addToHistory(current_jid, mess_data['to'], | 600 self.memory.addToHistory(current_jid, mess_data['to'], |
551 unicode(mess_data["message"]), | 601 unicode(mess_data["message"]), |
552 unicode(mess_data["type"]), | 602 unicode(mess_data["type"]), |
553 mess_data['extra'], | 603 mess_data['extra'], |
554 profile=profile) | 604 profile=profile) |
555 # We send back the message, so all clients are aware of it | 605 # We send back the message, so all clients are aware of it |
556 if mess_data["message"]: | 606 self.bridge.newMessage(mess_data['xml']['from'], |
557 self.bridge.newMessage(mess_data['xml']['from'], | 607 unicode(mess_data["message"]), |
558 unicode(mess_data["message"]), | 608 mess_type=mess_data["type"], |
559 mess_type=mess_data["type"], | 609 to_jid=mess_data['xml']['to'], |
560 to_jid=mess_data['xml']['to'], extra=mess_data['extra'], | 610 extra=mess_data['extra'], |
561 profile=profile) | 611 profile=profile) |
562 | |
563 treatments.addCallback(sendAndStore) | |
564 treatments.callback(mess_data) | |
565 | 612 |
566 def setPresence(self, to="", show="", priority=0, statuses=None, profile_key='@NONE@'): | 613 def setPresence(self, to="", show="", priority=0, statuses=None, profile_key='@NONE@'): |
567 """Send our presence information""" | 614 """Send our presence information""" |
568 if statuses is None: | 615 if statuses is None: |
569 statuses = {} | 616 statuses = {} |
621 assert(profile) | 668 assert(profile) |
622 to_jid = jid.JID(to) | 669 to_jid = jid.JID(to) |
623 self.profiles[profile].roster.removeItem(to_jid) | 670 self.profiles[profile].roster.removeItem(to_jid) |
624 self.profiles[profile].presence.unsubscribe(to_jid) | 671 self.profiles[profile].presence.unsubscribe(to_jid) |
625 | 672 |
673 def requestServerDisco(self, feature, jid_=None, cache_only=False, profile_key="@NONE"): | |
674 """Discover if a server or its items offer a given feature | |
675 @param feature: the feature to check | |
676 @param jid_: the jid of the server | |
677 @param cache_only: expect the result to be in cache and don't actually | |
678 make any request to avoid returning a Deferred. This can be used anytime | |
679 for requesting the local server because the data are cached for sure. | |
680 @result: the Deferred entity jid offering the feature, or None | |
681 """ | |
682 profile = self.memory.getProfileName(profile_key) | |
683 | |
684 if not profile: | |
685 return defer.succeed(None) | |
686 if jid_ is None: | |
687 jid_ = self.getClientHostJid(profile) | |
688 cache_only = True | |
689 hasServerFeature = lambda entity: entity if self.memory.hasServerFeature(feature, entity, profile) else None | |
690 | |
691 def haveItemsFeature(dummy=None): | |
692 if jid_ in self.memory.server_identities[profile]: | |
693 for entity in self.memory.server_identities[profile][jid_].values(): | |
694 if hasServerFeature(entity): | |
695 return entity | |
696 return None | |
697 | |
698 entity = hasServerFeature(jid_) or haveItemsFeature() | |
699 if entity: | |
700 return defer.succeed(entity) | |
701 elif entity is False or cache_only: | |
702 return defer.succeed(None) | |
703 | |
704 # data for this server are not in cache | |
705 disco = self.profiles[profile].disco | |
706 | |
707 def errback(failure, method, jid_, profile): | |
708 # the target server is not reachable | |
709 logging.error("disco.%s on %s failed! [%s]" % (method.func_name, jid_, profile)) | |
710 logging.error("reason: %s" % failure.getErrorMessage()) | |
711 if method == disco.requestInfo: | |
712 features = self.memory.server_features.setdefault(profile, {}) | |
713 features.setdefault(jid_, []) | |
714 elif method == disco.requestItems: | |
715 identities = self.memory.server_identities.setdefault(profile, {}) | |
716 identities.setdefault(jid_, {}) | |
717 return failure | |
718 | |
719 def callback(d): | |
720 if hasServerFeature(jid_): | |
721 return jid_ | |
722 else: | |
723 d2 = disco.requestItems(jid_).addCallback(self.serverDiscoItems, disco, jid_, profile) | |
724 d2.addErrback(errback, disco.requestItems, jid_, profile) | |
725 return d2.addCallback(haveItemsFeature) | |
726 | |
727 d = disco.requestInfo(jid_).addCallback(self.serverDisco, jid_, profile) | |
728 d.addCallbacks(callback, errback, [], [disco.requestInfo, jid_, profile]) | |
729 return d | |
730 | |
626 ## callbacks ## | 731 ## callbacks ## |
627 | 732 |
628 def serverDisco(self, disco, profile): | 733 def serverDisco(self, disco, jid_=None, profile=None): |
629 """xep-0030 Discovery Protocol.""" | 734 """xep-0030 Discovery Protocol. |
735 @param disco: result of the disco info query | |
736 @param jid_: the jid of the target server | |
737 @param profile: profile of the user | |
738 """ | |
739 if jid_ is None: | |
740 jid_ = self.getClientHostJid(profile) | |
741 debug(_("Requested disco info on %s") % jid_) | |
630 for feature in disco.features: | 742 for feature in disco.features: |
631 debug(_("Feature found: %s"), feature) | 743 debug(_("Feature found: %s") % feature) |
632 self.memory.addServerFeature(feature, profile) | 744 self.memory.addServerFeature(feature, jid_, profile) |
633 for cat, type in disco.identities: | 745 for cat, type_ in disco.identities: |
634 debug(_("Identity found: [%(category)s/%(type)s] %(identity)s") % {'category': cat, 'type': type, 'identity': disco.identities[(cat, type)]}) | 746 debug(_("Identity found: [%(category)s/%(type)s] %(identity)s") |
635 | 747 % {'category': cat, 'type': type_, 'identity': disco.identities[(cat, type_)]}) |
636 def serverDiscoItems(self, disco_result, disco_client, profile, initialized): | 748 |
749 def serverDiscoItems(self, disco_result, disco_client, jid_, profile, initialized=None): | |
637 """xep-0030 Discovery Protocol. | 750 """xep-0030 Discovery Protocol. |
638 @param disco_result: result of the disco item querry | 751 @param disco_result: result of the disco item querry |
639 @param disco_client: SatDiscoProtocol instance | 752 @param disco_client: SatDiscoProtocol instance |
753 @param jid_: the jid of the target server | |
640 @param profile: profile of the user | 754 @param profile: profile of the user |
641 @param initialized: deferred which must be chained when everything is done""" | 755 @param initialized: deferred which must be chained when everything is done""" |
642 | 756 |
643 def _check_entity_cb(result, entity, profile): | 757 def _check_entity_cb(result, entity, jid_, profile): |
644 for category, type in result.identities: | 758 debug(_("Requested disco info on %s") % entity) |
645 debug(_('Identity added: (%(category)s,%(type)s) ==> %(entity)s [%(profile)s]') % { | 759 for category, type_ in result.identities: |
646 'category': category, 'type': type, 'entity': entity, 'profile': profile}) | 760 debug(_('Identity added: (%(category)s,%(type)s) ==> %(entity)s [%(profile)s]') |
647 self.memory.addServerIdentity(category, type, entity, profile) | 761 % {'category': category, 'type': type_, 'entity': entity, 'profile': profile}) |
648 | 762 self.memory.addServerIdentity(category, type_, entity, jid_, profile) |
649 def _errback(result, entity, profile): | 763 for feature in result.features: |
764 self.memory.addServerFeature(feature, entity, profile) | |
765 | |
766 def _errback(result, entity, jid_, profile): | |
650 warning(_("Can't get information on identity [%(entity)s] for profile [%(profile)s]") % {'entity': entity, 'profile': profile}) | 767 warning(_("Can't get information on identity [%(entity)s] for profile [%(profile)s]") % {'entity': entity, 'profile': profile}) |
651 | 768 |
652 defer_list = [] | 769 defer_list = [] |
653 for item in disco_result._items: | 770 for item in disco_result._items: |
654 if item.entity.full().count('.') == 1: # XXX: workaround for a bug on jabberfr, tmp | 771 if item.entity.full().count('.') == 1: # XXX: workaround for a bug on jabberfr, tmp |
655 warning(_('Using jabberfr workaround, be sure your domain has at least two levels (e.g. "example.tld", not "example" alone)')) | 772 warning(_('Using jabberfr workaround, be sure your domain has at least two levels (e.g. "example.tld", not "example" alone)')) |
656 continue | 773 continue |
657 args = [item.entity, profile] | 774 args = [item.entity, jid_, profile] |
658 defer_list.append(disco_client.requestInfo(item.entity).addCallbacks(_check_entity_cb, _errback, args, None, args)) | 775 defer_list.append(disco_client.requestInfo(item.entity).addCallbacks(_check_entity_cb, _errback, args, None, args)) |
659 defer.DeferredList(defer_list).chainDeferred(initialized) | 776 if initialized: |
777 defer.DeferredList(defer_list).chainDeferred(initialized) | |
778 | |
660 ## Generic HMI ## | 779 ## Generic HMI ## |
661 | 780 |
662 def actionResult(self, action_id, action_type, data, profile): | 781 def actionResult(self, action_id, action_type, data, profile): |
663 """Send the result of an action | 782 """Send the result of an action |
664 @param action_id: same action_id used with action | 783 @param action_id: same action_id used with action |