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