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
This commit is contained in:
Goffi 2014-03-28 18:07:22 +01:00
commit 95678d7967
4 changed files with 431 additions and 479 deletions

View file

@ -28,6 +28,7 @@ class Const(object):
APP_NAME_SHORT = u'SàT'
APP_NAME_FULL = u'%s (%s)' % (APP_NAME_SHORT, APP_NAME)
APP_VERSION = u'0.4.1D' # Please add 'D' at the end for dev versions
APP_URL = 'http://www.salut-a-toi.org'
## Parameters ##
@ -40,7 +41,8 @@ class Const(object):
PROF_KEY_NONE = '@NONE@'
PROF_KEY_DEFAULT = '@DEFAULT@'
ENTITY_ALL = '@ALL@'
ENTITY_LAST_RESOURCE = 'last_resource'
ENTITY_LAST_RESOURCE = 'LAST_RESOURCE'
ENTITY_CAP_HASH = 'CAP_HASH'
## Configuration ##

View file

@ -74,6 +74,10 @@ class InternalError(Exception):
pass
class FeatureNotFound(Exception): # a disco feature/identity which is needed is not present
pass
class BridgeInitError(Exception):
pass

View file

@ -43,11 +43,6 @@ from sat.tools.misc import TriggerManager
from glob import glob
from uuid import uuid4
try:
from twisted.words.protocols.xmlstream import XMPPHandler
except ImportError:
from wokkel.subprotocols import XMPPHandler
### logging configuration FIXME: put this elsewhere ###
logging.basicConfig(level=logging.DEBUG,
format='%(message)s')
@ -217,16 +212,6 @@ class SAT(service.Service):
info(_("already connected !"))
return defer.succeed("None")
if profile in self.profiles:
# avoid the following error when self.connect() is called twice for the same profile within a short time period:
# Jumping into debugger for post-mortem of exception ''SatXMPPClient' object has no attribute 'discoHandler'':
# > /usr/local/lib/python2.7/dist-packages/sat/plugins/plugin_xep_0115.py(151)generateHash()
# -> services = client.discoHandler.info(client.jid, client.jid, '').addCallback(generateHash_2, profile)
# This is a strange issue that is often happening on my system since libervia is being run as a twisted plugin.
# FIXME: properly find the problem an fix it
debug("being connected...")
return defer.succeed("None")
def afterMemoryInit(ignore):
"""This part must be called when we have loaded individual parameters from memory"""
try:
@ -262,17 +247,20 @@ class SAT(service.Service):
debug(_("setting plugins parents"))
plugin_conn_cb = []
for plugin in self.plugins.iteritems():
if plugin[1].is_handler:
plugin[1].getHandler(profile).setHandlerParent(current)
connected_cb = getattr(plugin[1], "profileConnected", None)
if connected_cb:
connected_cb(profile)
plugin_conn_cb.append(connected_cb)
current.startService()
d = current.getConnectionDeferred()
d.addCallback(lambda x: current.roster.got_roster) # we want to be sure that we got the roster
d.addCallback(lambda dummy: current.roster.got_roster) # we want to be sure that we got the roster
for callback in plugin_conn_cb:
d.addCallback(lambda dummy: callback(profile))
return d
self.memory.startProfileSession(profile)
@ -360,14 +348,6 @@ class SAT(service.Service):
raise exceptions.ProfileKeyUnknownError
return [self.profiles[profile]]
def getClientHostJid(self, profile_key):
"""Convenient method to get the client host from profile key
@return: host jid or None if it doesn't exist"""
profile = self.memory.getProfileName(profile_key)
if not profile:
return None
return self.profiles[profile].getHostJid()
def registerNewAccount(self, login, password, email, server, port=5222, id_=None, profile_key=C.PROF_KEY_NONE):
"""Connect to a server and create a new account using in-band registration"""
profile = self.memory.getProfileName(profile_key)
@ -623,113 +603,26 @@ class SAT(service.Service):
self.profiles[profile].roster.removeItem(to_jid)
self.profiles[profile].presence.unsubscribe(to_jid)
def requestServerDisco(self, feature, jid_=None, cache_only=False, profile_key="@NONE"):
"""Discover if a server or its items offer a given feature
@param feature: the feature to check
@param jid_: the jid of the server, local server if None
@param cache_only: expect the result to be in cache and don't actually make any request.
This can be used anytime for requesting a feature on the local server because the data are cached for sure.
@result: the Deferred entity jid offering the feature, or None
"""
profile = self.memory.getProfileName(profile_key)
## Discovery ##
# discovery methods are shortcuts to self.memory.disco
# the main difference with client.disco is that self.memory.disco manage cache
if not profile:
return defer.succeed(None)
if jid_ is None:
jid_ = self.getClientHostJid(profile)
cache_only = True
hasServerFeature = lambda entity: entity if self.memory.hasServerFeature(feature, entity, profile) else None
def hasFeature(self, *args, **kwargs):
return self.memory.disco.hasFeature(*args, **kwargs)
def haveItemsFeature(dummy=None):
entities = self.memory.getAllServerIdentities(jid_, profile)
if entities is None:
return None # no cached data for this server
for entity in entities:
if hasServerFeature(entity):
return entity
return None # data are cached but no entity was found
def getDiscoInfos(self, *args, **kwargs):
return self.memory.disco.getInfos(*args, **kwargs)
entity = hasServerFeature(jid_) or haveItemsFeature()
if entity:
return defer.succeed(entity)
elif entity is False or cache_only:
return defer.succeed(None)
def getDiscoItems(self, *args, **kwargs):
return self.memory.disco.getItems(*args, **kwargs)
# data for this server are not in cache
disco = self.profiles[profile].disco
def findServiceEntities(self, *args, **kwargs):
return self.memory.disco.findServiceEntities(*args, **kwargs)
def errback(failure, method, jid_, profile):
# the target server is not reachable
logging.error("disco.%s on %s failed! [%s]" % (method.func_name, jid_.userhost(), profile))
logging.error("reason: %s" % failure.getErrorMessage())
if method == disco.requestInfo:
features = self.memory.server_features.setdefault(profile, {})
features.setdefault(jid_, [])
elif method == disco.requestItems:
identities = self.memory.server_identities.setdefault(profile, {})
identities.setdefault(jid_, {})
return failure
def findFeaturesSet(self, *args, **kwargs):
return self.memory.disco.findFeaturesSet(*args, **kwargs)
def callback(d):
if hasServerFeature(jid_):
return jid_
else:
d2 = disco.requestItems(jid_).addCallback(self.serverDiscoItems, disco, jid_, profile)
d2.addErrback(errback, disco.requestItems, jid_, profile)
return d2.addCallback(haveItemsFeature)
d = disco.requestInfo(jid_).addCallback(self.serverDisco, jid_, profile)
d.addCallbacks(callback, errback, [], errbackArgs=[disco.requestInfo, jid_, profile])
return d
## callbacks ##
def serverDisco(self, disco, jid_=None, profile=None):
"""xep-0030 Discovery Protocol.
@param disco: result of the disco info query
@param jid_: the jid of the target server
@param profile: profile of the user
"""
if jid_ is None:
jid_ = self.getClientHostJid(profile)
debug(_("Requested disco info on %s") % jid_)
for feature in disco.features:
debug(_("Feature found: %s") % feature)
self.memory.addServerFeature(feature, jid_, profile)
for cat, type_ in disco.identities:
debug(_("Identity found: [%(category)s/%(type)s] %(identity)s")
% {'category': cat, 'type': type_, 'identity': disco.identities[(cat, type_)]})
def serverDiscoItems(self, disco_result, disco_client, jid_, profile, initialized=None):
"""xep-0030 Discovery Protocol.
@param disco_result: result of the disco item querry
@param disco_client: SatDiscoProtocol instance
@param jid_: the jid of the target server
@param profile: profile of the user
@param initialized: deferred which must be chained when everything is done"""
def _check_entity_cb(result, entity, jid_, profile):
debug(_("Requested disco info on %s") % entity)
for category, type_ in result.identities:
debug(_('Identity added: (%(category)s,%(type)s) ==> %(entity)s [%(profile)s]')
% {'category': category, 'type': type_, 'entity': entity, 'profile': profile})
self.memory.addServerIdentity(category, type_, entity, jid_, profile)
for feature in result.features:
self.memory.addServerFeature(feature, entity, profile)
def _errback(result, entity, jid_, profile):
warning(_("Can't get information on identity [%(entity)s] for profile [%(profile)s]") % {'entity': entity, 'profile': profile})
defer_list = []
for item in disco_result._items:
if item.entity.full().count('.') == 1: # XXX: workaround for a bug on jabberfr, tmp
warning(_('Using jabberfr workaround, be sure your domain has at least two levels (e.g. "example.tld", not "example" alone)'))
continue
args = [item.entity, jid_, profile]
defer_list.append(disco_client.requestInfo(item.entity).addCallbacks(_check_entity_cb, _errback, args, None, args))
if initialized:
defer.DeferredList(defer_list).chainDeferred(initialized)
## Generic HMI ##

View file

@ -41,7 +41,6 @@ class SatXMPPClient(client.XMPPClient):
self.__connected = False
self.profile = profile
self.host_app = host_app
self.client_initialized = defer.Deferred()
self.conn_deferred = defer.Deferred()
self._waiting_conf = {} # callback called when a confirmation is received
self._progress_cb_map = {} # callback called when a progress is requested (key = progress id)
@ -69,19 +68,17 @@ class SatXMPPClient(client.XMPPClient):
self.disco.setHandlerParent(self)
self.discoHandler = disco.DiscoHandler()
self.discoHandler.setHandlerParent(self)
disco_d = defer.succeed(None)
if not self.host_app.trigger.point("Disco Handled", self.profile):
if not self.host_app.trigger.point("Disco handled", disco_d, self.profile):
return
self.roster.requestRoster()
def finish_connection(dummy):
self.roster.requestRoster()
self.presence.available()
self.conn_deferred.callback(None)
self.presence.available()
jid_ = self.getHostJid()
self.disco.requestInfo(jid_).addCallback(self.host_app.serverDisco, jid_, self.profile) # FIXME: use these informations
self.disco.requestItems(jid_).addCallback(self.host_app.serverDiscoItems, self.disco, jid_, self.profile, self.client_initialized)
self.conn_deferred.callback(None)
disco_d.addCallback(finish_connection)
def initializationFailed(self, reason):
error(_("ERROR: XMPP connection failed for profile '%(profile)s': %(reason)s" % {'profile': self.profile, 'reason': reason}))
@ -106,10 +103,6 @@ class SatXMPPClient(client.XMPPClient):
self.host_app.bridge.disconnected(self.profile) # we send the signal to the clients
self.host_app.purgeClient(self.profile) # and we remove references to this client
def getHostJid(self):
"""@return: the jid of the local server"""
return jid.JID(self.jid.host)
class SatMessageProtocol(xmppim.MessageProtocol):
@ -292,6 +285,11 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
xmppim.PresenceClientProtocol.__init__(self)
self.host = host
def send(self, obj):
if not self.host.trigger.point("Presence send", obj):
return
super(SatPresenceProtocol, self).send(obj)
def availableReceived(self, entity, show=None, statuses=None, priority=0):
debug(_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity': entity, 'show': show, 'statuses': statuses, 'priority': priority})
@ -434,7 +432,7 @@ class RegisteringAuthenticator(xmlstream.ConnectAuthenticator):
if self.user_email:
_email = query.addElement('email')
_email.addContent(self.user_email)
reg = iq.send(self.jabber_host).addCallbacks(self.registrationAnswer, self.registrationFailure)
iq.send(self.jabber_host).addCallbacks(self.registrationAnswer, self.registrationFailure)
def registrationAnswer(self, answer):
debug(_("registration answer: %s") % answer.toXml())