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:
parent
382d49a628
commit
95678d7967
4 changed files with 431 additions and 479 deletions
|
|
@ -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 ##
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ##
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Reference in a new issue