Mercurial > libervia-backend
comparison src/core/sat_main.py @ 944:e1842ebcb2f3
core, plugin XEP-0115: discovery refactoring:
- hashing algorithm of XEP-0115 has been including in core
- our own hash is still calculated by XEP-0115 and can be regenerated with XEP_0115.recalculateHash
- old discovery methods have been removed. Now the following methods are used:
- hasFeature: tell if a feature is available for an entity
- getDiscoInfos: self explaining
- getDiscoItems: self explaining
- findServiceEntities: return all available items of an entity which given (category, type)
- findFeaturesSet: search for a set of features in entity + entity's items
all these methods are asynchronous, and manage cache automatically
- XEP-0115 manage in a better way hashes, and now use a trigger for presence instead of monkey patch
- new FeatureNotFound exception, when we want to do something which is not available
- refactored client initialisation sequence, removed client.initialized Deferred
- added constant APP_URL
- test_plugin_xep_0033.py has been temporarly deactivated, the time to adapt it
- lot of cleaning
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 28 Mar 2014 18:07:22 +0100 |
parents | 71926ec2114d |
children | b4cd968e30fb |
comparison
equal
deleted
inserted
replaced
943:71926ec2114d | 944:e1842ebcb2f3 |
---|---|
40 from sat.core.constants import Const as C | 40 from sat.core.constants import Const as C |
41 from sat.memory.memory import Memory | 41 from sat.memory.memory import Memory |
42 from sat.tools.misc import TriggerManager | 42 from sat.tools.misc import TriggerManager |
43 from glob import glob | 43 from glob import glob |
44 from uuid import uuid4 | 44 from uuid import uuid4 |
45 | |
46 try: | |
47 from twisted.words.protocols.xmlstream import XMPPHandler | |
48 except ImportError: | |
49 from wokkel.subprotocols import XMPPHandler | |
50 | 45 |
51 ### logging configuration FIXME: put this elsewhere ### | 46 ### logging configuration FIXME: put this elsewhere ### |
52 logging.basicConfig(level=logging.DEBUG, | 47 logging.basicConfig(level=logging.DEBUG, |
53 format='%(message)s') | 48 format='%(message)s') |
54 ### | 49 ### |
215 | 210 |
216 if self.isConnected(profile): | 211 if self.isConnected(profile): |
217 info(_("already connected !")) | 212 info(_("already connected !")) |
218 return defer.succeed("None") | 213 return defer.succeed("None") |
219 | 214 |
220 if profile in self.profiles: | |
221 # avoid the following error when self.connect() is called twice for the same profile within a short time period: | |
222 # Jumping into debugger for post-mortem of exception ''SatXMPPClient' object has no attribute 'discoHandler'': | |
223 # > /usr/local/lib/python2.7/dist-packages/sat/plugins/plugin_xep_0115.py(151)generateHash() | |
224 # -> services = client.discoHandler.info(client.jid, client.jid, '').addCallback(generateHash_2, profile) | |
225 # This is a strange issue that is often happening on my system since libervia is being run as a twisted plugin. | |
226 # FIXME: properly find the problem an fix it | |
227 debug("being connected...") | |
228 return defer.succeed("None") | |
229 | |
230 def afterMemoryInit(ignore): | 215 def afterMemoryInit(ignore): |
231 """This part must be called when we have loaded individual parameters from memory""" | 216 """This part must be called when we have loaded individual parameters from memory""" |
232 try: | 217 try: |
233 port = int(self.memory.getParamA("Port", "Connection", profile_key=profile)) | 218 port = int(self.memory.getParamA("Port", "Connection", profile_key=profile)) |
234 except ValueError: | 219 except ValueError: |
260 current.identityHandler = xmpp.SatIdentityHandler() | 245 current.identityHandler = xmpp.SatIdentityHandler() |
261 current.identityHandler.setHandlerParent(current) | 246 current.identityHandler.setHandlerParent(current) |
262 | 247 |
263 debug(_("setting plugins parents")) | 248 debug(_("setting plugins parents")) |
264 | 249 |
250 plugin_conn_cb = [] | |
265 for plugin in self.plugins.iteritems(): | 251 for plugin in self.plugins.iteritems(): |
266 if plugin[1].is_handler: | 252 if plugin[1].is_handler: |
267 plugin[1].getHandler(profile).setHandlerParent(current) | 253 plugin[1].getHandler(profile).setHandlerParent(current) |
268 connected_cb = getattr(plugin[1], "profileConnected", None) | 254 connected_cb = getattr(plugin[1], "profileConnected", None) |
269 if connected_cb: | 255 if connected_cb: |
270 connected_cb(profile) | 256 plugin_conn_cb.append(connected_cb) |
271 | 257 |
272 current.startService() | 258 current.startService() |
273 | 259 |
274 d = current.getConnectionDeferred() | 260 d = current.getConnectionDeferred() |
275 d.addCallback(lambda x: current.roster.got_roster) # we want to be sure that we got the roster | 261 d.addCallback(lambda dummy: current.roster.got_roster) # we want to be sure that we got the roster |
262 for callback in plugin_conn_cb: | |
263 d.addCallback(lambda dummy: callback(profile)) | |
276 return d | 264 return d |
277 | 265 |
278 self.memory.startProfileSession(profile) | 266 self.memory.startProfileSession(profile) |
279 return self.memory.loadIndividualParams(profile).addCallback(afterMemoryInit) | 267 return self.memory.loadIndividualParams(profile).addCallback(afterMemoryInit) |
280 | 268 |
357 if profile == "@ALL@": | 345 if profile == "@ALL@": |
358 return self.profiles.values() | 346 return self.profiles.values() |
359 if profile.count('@') > 1: | 347 if profile.count('@') > 1: |
360 raise exceptions.ProfileKeyUnknownError | 348 raise exceptions.ProfileKeyUnknownError |
361 return [self.profiles[profile]] | 349 return [self.profiles[profile]] |
362 | |
363 def getClientHostJid(self, profile_key): | |
364 """Convenient method to get the client host from profile key | |
365 @return: host jid or None if it doesn't exist""" | |
366 profile = self.memory.getProfileName(profile_key) | |
367 if not profile: | |
368 return None | |
369 return self.profiles[profile].getHostJid() | |
370 | 350 |
371 def registerNewAccount(self, login, password, email, server, port=5222, id_=None, profile_key=C.PROF_KEY_NONE): | 351 def registerNewAccount(self, login, password, email, server, port=5222, id_=None, profile_key=C.PROF_KEY_NONE): |
372 """Connect to a server and create a new account using in-band registration""" | 352 """Connect to a server and create a new account using in-band registration""" |
373 profile = self.memory.getProfileName(profile_key) | 353 profile = self.memory.getProfileName(profile_key) |
374 assert(profile) | 354 assert(profile) |
621 profile = self.memory.getProfileName(profile_key) | 601 profile = self.memory.getProfileName(profile_key) |
622 assert(profile) | 602 assert(profile) |
623 self.profiles[profile].roster.removeItem(to_jid) | 603 self.profiles[profile].roster.removeItem(to_jid) |
624 self.profiles[profile].presence.unsubscribe(to_jid) | 604 self.profiles[profile].presence.unsubscribe(to_jid) |
625 | 605 |
626 def requestServerDisco(self, feature, jid_=None, cache_only=False, profile_key="@NONE"): | 606 |
627 """Discover if a server or its items offer a given feature | 607 ## Discovery ## |
628 @param feature: the feature to check | 608 # discovery methods are shortcuts to self.memory.disco |
629 @param jid_: the jid of the server, local server if None | 609 # the main difference with client.disco is that self.memory.disco manage cache |
630 @param cache_only: expect the result to be in cache and don't actually make any request. | 610 |
631 This can be used anytime for requesting a feature on the local server because the data are cached for sure. | 611 def hasFeature(self, *args, **kwargs): |
632 @result: the Deferred entity jid offering the feature, or None | 612 return self.memory.disco.hasFeature(*args, **kwargs) |
633 | 613 |
634 """ | 614 def getDiscoInfos(self, *args, **kwargs): |
635 profile = self.memory.getProfileName(profile_key) | 615 return self.memory.disco.getInfos(*args, **kwargs) |
636 | 616 |
637 if not profile: | 617 def getDiscoItems(self, *args, **kwargs): |
638 return defer.succeed(None) | 618 return self.memory.disco.getItems(*args, **kwargs) |
639 if jid_ is None: | 619 |
640 jid_ = self.getClientHostJid(profile) | 620 def findServiceEntities(self, *args, **kwargs): |
641 cache_only = True | 621 return self.memory.disco.findServiceEntities(*args, **kwargs) |
642 hasServerFeature = lambda entity: entity if self.memory.hasServerFeature(feature, entity, profile) else None | 622 |
643 | 623 def findFeaturesSet(self, *args, **kwargs): |
644 def haveItemsFeature(dummy=None): | 624 return self.memory.disco.findFeaturesSet(*args, **kwargs) |
645 entities = self.memory.getAllServerIdentities(jid_, profile) | 625 |
646 if entities is None: | |
647 return None # no cached data for this server | |
648 for entity in entities: | |
649 if hasServerFeature(entity): | |
650 return entity | |
651 return None # data are cached but no entity was found | |
652 | |
653 entity = hasServerFeature(jid_) or haveItemsFeature() | |
654 if entity: | |
655 return defer.succeed(entity) | |
656 elif entity is False or cache_only: | |
657 return defer.succeed(None) | |
658 | |
659 # data for this server are not in cache | |
660 disco = self.profiles[profile].disco | |
661 | |
662 def errback(failure, method, jid_, profile): | |
663 # the target server is not reachable | |
664 logging.error("disco.%s on %s failed! [%s]" % (method.func_name, jid_.userhost(), profile)) | |
665 logging.error("reason: %s" % failure.getErrorMessage()) | |
666 if method == disco.requestInfo: | |
667 features = self.memory.server_features.setdefault(profile, {}) | |
668 features.setdefault(jid_, []) | |
669 elif method == disco.requestItems: | |
670 identities = self.memory.server_identities.setdefault(profile, {}) | |
671 identities.setdefault(jid_, {}) | |
672 return failure | |
673 | |
674 def callback(d): | |
675 if hasServerFeature(jid_): | |
676 return jid_ | |
677 else: | |
678 d2 = disco.requestItems(jid_).addCallback(self.serverDiscoItems, disco, jid_, profile) | |
679 d2.addErrback(errback, disco.requestItems, jid_, profile) | |
680 return d2.addCallback(haveItemsFeature) | |
681 | |
682 d = disco.requestInfo(jid_).addCallback(self.serverDisco, jid_, profile) | |
683 d.addCallbacks(callback, errback, [], errbackArgs=[disco.requestInfo, jid_, profile]) | |
684 return d | |
685 | |
686 ## callbacks ## | |
687 | |
688 def serverDisco(self, disco, jid_=None, profile=None): | |
689 """xep-0030 Discovery Protocol. | |
690 @param disco: result of the disco info query | |
691 @param jid_: the jid of the target server | |
692 @param profile: profile of the user | |
693 """ | |
694 if jid_ is None: | |
695 jid_ = self.getClientHostJid(profile) | |
696 debug(_("Requested disco info on %s") % jid_) | |
697 for feature in disco.features: | |
698 debug(_("Feature found: %s") % feature) | |
699 self.memory.addServerFeature(feature, jid_, profile) | |
700 for cat, type_ in disco.identities: | |
701 debug(_("Identity found: [%(category)s/%(type)s] %(identity)s") | |
702 % {'category': cat, 'type': type_, 'identity': disco.identities[(cat, type_)]}) | |
703 | |
704 def serverDiscoItems(self, disco_result, disco_client, jid_, profile, initialized=None): | |
705 """xep-0030 Discovery Protocol. | |
706 @param disco_result: result of the disco item querry | |
707 @param disco_client: SatDiscoProtocol instance | |
708 @param jid_: the jid of the target server | |
709 @param profile: profile of the user | |
710 @param initialized: deferred which must be chained when everything is done""" | |
711 | |
712 def _check_entity_cb(result, entity, jid_, profile): | |
713 debug(_("Requested disco info on %s") % entity) | |
714 for category, type_ in result.identities: | |
715 debug(_('Identity added: (%(category)s,%(type)s) ==> %(entity)s [%(profile)s]') | |
716 % {'category': category, 'type': type_, 'entity': entity, 'profile': profile}) | |
717 self.memory.addServerIdentity(category, type_, entity, jid_, profile) | |
718 for feature in result.features: | |
719 self.memory.addServerFeature(feature, entity, profile) | |
720 | |
721 def _errback(result, entity, jid_, profile): | |
722 warning(_("Can't get information on identity [%(entity)s] for profile [%(profile)s]") % {'entity': entity, 'profile': profile}) | |
723 | |
724 defer_list = [] | |
725 for item in disco_result._items: | |
726 if item.entity.full().count('.') == 1: # XXX: workaround for a bug on jabberfr, tmp | |
727 warning(_('Using jabberfr workaround, be sure your domain has at least two levels (e.g. "example.tld", not "example" alone)')) | |
728 continue | |
729 args = [item.entity, jid_, profile] | |
730 defer_list.append(disco_client.requestInfo(item.entity).addCallbacks(_check_entity_cb, _errback, args, None, args)) | |
731 if initialized: | |
732 defer.DeferredList(defer_list).chainDeferred(initialized) | |
733 | 626 |
734 ## Generic HMI ## | 627 ## Generic HMI ## |
735 | 628 |
736 def actionResult(self, action_id, action_type, data, profile): | 629 def actionResult(self, action_id, action_type, data, profile): |
737 """Send the result of an action | 630 """Send the result of an action |