Add caching support to xep_0030.

New plugin configuration options:

    use_cache    - Enable caching disco info results. Defaults to True
    wrap_results - Always return disco results in an Iq stanza. Defaults
                   to False

Node handler changes:

    Handlers now take four arguments: jid, node, ifrom, data

    Most older style handlers will still work, depending on if they
    raise a TypeError for incorrect number of arguments. Handlers that
    used *args may not work.

New get_info options:

    cached - Passing cached=True to get_info() will attempt to load
             results from the cache. If nothing is found, a query
             will be sent as normal. If set to False, the cache
             will be skipped, even if it contains results.

New method:

    supports() - Given a JID/node pair and a feature, return True
                 if the feature is supported, False if not, and
                 None if there was a timeout. By default, the search
                 will use the cache.
This commit is contained in:
Lance Stout 2011-12-28 10:07:33 -05:00
parent 1a61bdb302
commit d979b5f2b9
5 changed files with 339 additions and 146 deletions

View file

@ -10,7 +10,7 @@ import logging
import sleekxmpp import sleekxmpp
from sleekxmpp import Iq from sleekxmpp import Iq
from sleekxmpp.exceptions import XMPPError from sleekxmpp.exceptions import XMPPError, IqError, IqTimeout
from sleekxmpp.plugins.base import base_plugin from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath from sleekxmpp.xmlstream.matcher import StanzaPath
@ -108,11 +108,16 @@ class xep_0030(base_plugin):
self.static = StaticDisco(self.xmpp) self.static = StaticDisco(self.xmpp)
self.use_cache = self.config.get('use_cache', True)
self.wrap_results = self.config.get('wrap_results', False)
self._disco_ops = ['get_info', 'set_identities', 'set_features', self._disco_ops = ['get_info', 'set_identities', 'set_features',
'get_items', 'set_items', 'del_items', 'get_items', 'set_items', 'del_items',
'add_identity', 'del_identity', 'add_feature', 'add_identity', 'del_identity', 'add_feature',
'del_feature', 'add_item', 'del_item', 'del_feature', 'add_item', 'del_item',
'del_identities', 'del_features'] 'del_identities', 'del_features',
'cache_info', 'get_cached_info']
self.default_handlers = {} self.default_handlers = {}
self._handlers = {} self._handlers = {}
for op in self._disco_ops: for op in self._disco_ops:
@ -237,7 +242,47 @@ class xep_0030(base_plugin):
self.del_node_handler(op, jid, node) self.del_node_handler(op, jid, node)
self.set_node_handler(op, jid, node, self.default_handlers[op]) self.set_node_handler(op, jid, node, self.default_handlers[op])
def get_info(self, jid=None, node=None, local=False, **kwargs): def supports(self, jid=None, node=None, feature=None, local=False,
cached=True, ifrom=None):
"""
Check if a JID supports a given feature.
Return values:
True -- The feature is supported
False -- The feature is not listed as supported
None -- Nothing could be found due to a timeout
Arguments:
jid -- Request info from this JID.
node -- The particular node to query.
feature -- The name of the feature to check.
local -- If true, then the query is for a JID/node
combination handled by this Sleek instance and
no stanzas need to be sent.
Otherwise, a disco stanza must be sent to the
remove JID to retrieve the info.
cached -- If true, then look for the disco info data from
the local cache system. If no results are found,
send the query as usual. The self.use_cache
setting must be set to true for this option to
be useful. If set to false, then the cache will
be skipped, even if a result has already been
cached. Defaults to false.
ifrom -- Specifiy the sender's JID.
"""
try:
info = self.get_info(jid=jid, node=node, local=local,
cached=cached, ifrom=ifrom)
info = self._wrap(ifrom, jid, info, True)
features = info['disco_info']['features']
return feature in features
except IqError:
return False
except IqTimeout:
return None
def get_info(self, jid=None, node=None, local=False,
cached=None, **kwargs):
""" """
Retrieve the disco#info results from a given JID/node combination. Retrieve the disco#info results from a given JID/node combination.
@ -257,6 +302,13 @@ class xep_0030(base_plugin):
no stanzas need to be sent. no stanzas need to be sent.
Otherwise, a disco stanza must be sent to the Otherwise, a disco stanza must be sent to the
remove JID to retrieve the info. remove JID to retrieve the info.
cached -- If true, then look for the disco info data from
the local cache system. If no results are found,
send the query as usual. The self.use_cache
setting must be set to true for this option to
be useful. If set to false, then the cache will
be skipped, even if a result has already been
cached. Defaults to false.
ifrom -- Specifiy the sender's JID. ifrom -- Specifiy the sender's JID.
block -- If true, block and wait for the stanzas' reply. block -- If true, block and wait for the stanzas' reply.
timeout -- The time in seconds to block while waiting for timeout -- The time in seconds to block while waiting for
@ -269,8 +321,18 @@ class xep_0030(base_plugin):
if local or jid is None: if local or jid is None:
log.debug("Looking up local disco#info data " + \ log.debug("Looking up local disco#info data " + \
"for %s, node %s.", jid, node) "for %s, node %s.", jid, node)
info = self._run_node_handler('get_info', jid, node, kwargs) info = self._run_node_handler('get_info',
return self._fix_default_info(info) jid, node, kwargs.get('ifrom', None), kwargs)
info = self._fix_default_info(info)
return self._wrap(kwargs.get('ifrom', None), jid, info)
if cached:
log.debug("Looking up cached disco#info data " + \
"for %s, node %s.", jid, node)
info = self._run_node_handler('get_cached_info',
jid, node, kwargs.get('ifrom', None), kwargs)
if info is not None:
return self._wrap(kwargs.get('ifrom', None), jid, info)
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
# Check dfrom parameter for backwards compatibility # Check dfrom parameter for backwards compatibility
@ -314,7 +376,9 @@ class xep_0030(base_plugin):
Otherwise the parameter is ignored. Otherwise the parameter is ignored.
""" """
if local or jid is None: if local or jid is None:
return self._run_node_handler('get_items', jid, node, kwargs) items = self._run_node_handler('get_items',
jid, node, kwargs.get('ifrom', None), kwargs)
return self._wrap(kwargs.get('ifrom', None), jid, items)
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
# Check dfrom parameter for backwards compatibility # Check dfrom parameter for backwards compatibility
@ -341,7 +405,7 @@ class xep_0030(base_plugin):
node -- Optional node to modify. node -- Optional node to modify.
items -- A series of items in tuple format. items -- A series of items in tuple format.
""" """
self._run_node_handler('set_items', jid, node, kwargs) self._run_node_handler('set_items', jid, node, None, kwargs)
def del_items(self, jid=None, node=None, **kwargs): def del_items(self, jid=None, node=None, **kwargs):
""" """
@ -351,7 +415,7 @@ class xep_0030(base_plugin):
jid -- The JID to modify. jid -- The JID to modify.
node -- Optional node to modify. node -- Optional node to modify.
""" """
self._run_node_handler('del_items', jid, node, kwargs) self._run_node_handler('del_items', jid, node, None, kwargs)
def add_item(self, jid='', name='', node=None, subnode='', ijid=None): def add_item(self, jid='', name='', node=None, subnode='', ijid=None):
""" """
@ -372,7 +436,7 @@ class xep_0030(base_plugin):
kwargs = {'ijid': jid, kwargs = {'ijid': jid,
'name': name, 'name': name,
'inode': subnode} 'inode': subnode}
self._run_node_handler('add_item', ijid, node, kwargs) self._run_node_handler('add_item', ijid, node, None, kwargs)
def del_item(self, jid=None, node=None, **kwargs): def del_item(self, jid=None, node=None, **kwargs):
""" """
@ -384,7 +448,7 @@ class xep_0030(base_plugin):
ijid -- The item's JID. ijid -- The item's JID.
inode -- The item's node. inode -- The item's node.
""" """
self._run_node_handler('del_item', jid, node, kwargs) self._run_node_handler('del_item', jid, node, None, kwargs)
def add_identity(self, category='', itype='', name='', def add_identity(self, category='', itype='', name='',
node=None, jid=None, lang=None): node=None, jid=None, lang=None):
@ -411,7 +475,7 @@ class xep_0030(base_plugin):
'itype': itype, 'itype': itype,
'name': name, 'name': name,
'lang': lang} 'lang': lang}
self._run_node_handler('add_identity', jid, node, kwargs) self._run_node_handler('add_identity', jid, node, None, kwargs)
def add_feature(self, feature, node=None, jid=None): def add_feature(self, feature, node=None, jid=None):
""" """
@ -423,7 +487,7 @@ class xep_0030(base_plugin):
jid -- The JID to modify. jid -- The JID to modify.
""" """
kwargs = {'feature': feature} kwargs = {'feature': feature}
self._run_node_handler('add_feature', jid, node, kwargs) self._run_node_handler('add_feature', jid, node, None, kwargs)
def del_identity(self, jid=None, node=None, **kwargs): def del_identity(self, jid=None, node=None, **kwargs):
""" """
@ -437,7 +501,7 @@ class xep_0030(base_plugin):
name -- Optional, human readable name for the identity. name -- Optional, human readable name for the identity.
lang -- Optional, the identity's xml:lang value. lang -- Optional, the identity's xml:lang value.
""" """
self._run_node_handler('del_identity', jid, node, kwargs) self._run_node_handler('del_identity', jid, node, None, kwargs)
def del_feature(self, jid=None, node=None, **kwargs): def del_feature(self, jid=None, node=None, **kwargs):
""" """
@ -448,7 +512,7 @@ class xep_0030(base_plugin):
node -- The node to modify. node -- The node to modify.
feature -- The feature's namespace. feature -- The feature's namespace.
""" """
self._run_node_handler('del_feature', jid, node, kwargs) self._run_node_handler('del_feature', jid, node, None, kwargs)
def set_identities(self, jid=None, node=None, **kwargs): def set_identities(self, jid=None, node=None, **kwargs):
""" """
@ -463,7 +527,7 @@ class xep_0030(base_plugin):
identities -- A set of identities in tuple form. identities -- A set of identities in tuple form.
lang -- Optional, xml:lang value. lang -- Optional, xml:lang value.
""" """
self._run_node_handler('set_identities', jid, node, kwargs) self._run_node_handler('set_identities', jid, node, None, kwargs)
def del_identities(self, jid=None, node=None, **kwargs): def del_identities(self, jid=None, node=None, **kwargs):
""" """
@ -478,7 +542,7 @@ class xep_0030(base_plugin):
lang -- Optional. If given, only remove identities lang -- Optional. If given, only remove identities
using this xml:lang value. using this xml:lang value.
""" """
self._run_node_handler('del_identities', jid, node, kwargs) self._run_node_handler('del_identities', jid, node, None, kwargs)
def set_features(self, jid=None, node=None, **kwargs): def set_features(self, jid=None, node=None, **kwargs):
""" """
@ -490,7 +554,7 @@ class xep_0030(base_plugin):
node -- The node to modify. node -- The node to modify.
features -- The new set of supported features. features -- The new set of supported features.
""" """
self._run_node_handler('set_features', jid, node, kwargs) self._run_node_handler('set_features', jid, node, None, kwargs)
def del_features(self, jid=None, node=None, **kwargs): def del_features(self, jid=None, node=None, **kwargs):
""" """
@ -500,9 +564,9 @@ class xep_0030(base_plugin):
jid -- The JID to modify. jid -- The JID to modify.
node -- The node to modify. node -- The node to modify.
""" """
self._run_node_handler('del_features', jid, node, kwargs) self._run_node_handler('del_features', jid, node, None, kwargs)
def _run_node_handler(self, htype, jid, node, data={}): def _run_node_handler(self, htype, jid, node, ifrom, data={}):
""" """
Execute the most specific node handler for the given Execute the most specific node handler for the given
JID/node combination. JID/node combination.
@ -521,12 +585,26 @@ class xep_0030(base_plugin):
if node is None: if node is None:
node = '' node = ''
try:
args = (jid, node, ifrom, data)
if self._handlers[htype]['node'].get((jid, node), False): if self._handlers[htype]['node'].get((jid, node), False):
return self._handlers[htype]['node'][(jid, node)](jid, node, data) return self._handlers[htype]['node'][(jid, node)](*args)
elif self._handlers[htype]['jid'].get(jid, False): elif self._handlers[htype]['jid'].get(jid, False):
return self._handlers[htype]['jid'][jid](jid, node, data) return self._handlers[htype]['jid'][jid](*args)
elif self._handlers[htype]['global']: elif self._handlers[htype]['global']:
return self._handlers[htype]['global'](jid, node, data) return self._handlers[htype]['global'](*args)
else:
return None
except TypeError:
# To preserve backward compatibility, drop the ifrom parameter
# for existing handlers that don't understand it.
args = (jid, node, data)
if self._handlers[htype]['node'].get((jid, node), False):
return self._handlers[htype]['node'][(jid, node)](*args)
elif self._handlers[htype]['jid'].get(jid, False):
return self._handlers[htype]['jid'][jid](*args)
elif self._handlers[htype]['global']:
return self._handlers[htype]['global'](*args)
else: else:
return None return None
@ -550,6 +628,7 @@ class xep_0030(base_plugin):
info = self._run_node_handler('get_info', info = self._run_node_handler('get_info',
jid, jid,
iq['disco_info']['node'], iq['disco_info']['node'],
iq['from'],
iq) iq)
if isinstance(info, Iq): if isinstance(info, Iq):
info.send() info.send()
@ -560,8 +639,16 @@ class xep_0030(base_plugin):
iq.set_payload(info.xml) iq.set_payload(info.xml)
iq.send() iq.send()
elif iq['type'] == 'result': elif iq['type'] == 'result':
log.debug("Received disco info result from" + \ log.debug("Received disco info result from " + \
"%s to %s.", iq['from'], iq['to']) "<%s> to <%s>.", iq['from'], iq['to'])
if self.use_cache:
log.debug("Caching disco info result from " \
"<%s> to <%s>.", iq['from'], iq['to'])
self._run_node_handler('cache_info',
iq['from'].full,
iq['disco_info']['node'],
iq['to'].full,
iq)
self.xmpp.event('disco_info', iq) self.xmpp.event('disco_info', iq)
def _handle_disco_items(self, iq): def _handle_disco_items(self, iq):
@ -583,6 +670,7 @@ class xep_0030(base_plugin):
items = self._run_node_handler('get_items', items = self._run_node_handler('get_items',
jid, jid,
iq['disco_items']['node'], iq['disco_items']['node'],
iq['from'].full,
iq) iq)
if isinstance(items, Iq): if isinstance(items, Iq):
items.send() items.send()
@ -607,6 +695,9 @@ class xep_0030(base_plugin):
Arguments: Arguments:
info -- The disco#info quest (not the full Iq stanza) to modify. info -- The disco#info quest (not the full Iq stanza) to modify.
""" """
result = info
if isinstance(info, Iq):
info = iq['disco_info']
if not info['node']: if not info['node']:
if not info['identities']: if not info['identities']:
if self.xmpp.is_component: if self.xmpp.is_component:
@ -621,7 +712,29 @@ class xep_0030(base_plugin):
log.debug("No features found for this entity." + \ log.debug("No features found for this entity." + \
"Using default disco#info feature.") "Using default disco#info feature.")
info.add_feature(info.namespace) info.add_feature(info.namespace)
return info return result
def _wrap(self, ito, ifrom, payload, force=False):
"""
Ensure that results are wrapped in an Iq stanza
if self.wrap_results has been set to True.
Arguments:
ito -- The JID to use as the 'to' value
ifrom -- The JID to use as the 'from' value
payload -- The disco data to wrap
force -- Force wrapping, regardless of self.wrap_results
"""
if (force or self.wrap_results) and not isinstance(payload, Iq):
iq = self.xmpp.Iq()
# Since we're simulating a result, we have to treat
# the 'from' and 'to' values opposite the normal way.
iq['to'] = self.xmpp.boundjid if ito is None else ito
iq['from'] = self.xmpp.boundjid if ifrom is None else ifrom
iq['type'] = 'result'
iq.append(payload)
return iq
return payload
# Retain some backwards compatibility # Retain some backwards compatibility

View file

@ -7,6 +7,7 @@
""" """
import logging import logging
import threading
import sleekxmpp import sleekxmpp
from sleekxmpp import Iq from sleekxmpp import Iq
@ -50,8 +51,9 @@ class StaticDisco(object):
""" """
self.nodes = {} self.nodes = {}
self.xmpp = xmpp self.xmpp = xmpp
self.lock = threading.RLock()
def add_node(self, jid=None, node=None): def add_node(self, jid=None, node=None, ifrom=None):
""" """
Create a new set of stanzas for the provided Create a new set of stanzas for the provided
JID and node combination. JID and node combination.
@ -60,38 +62,77 @@ class StaticDisco(object):
jid -- The JID that will own the new stanzas. jid -- The JID that will own the new stanzas.
node -- The node that will own the new stanzas. node -- The node that will own the new stanzas.
""" """
with self.lock:
if jid is None: if jid is None:
jid = self.xmpp.boundjid.full jid = self.xmpp.boundjid.full
if node is None: if node is None:
node = '' node = ''
if (jid, node) not in self.nodes: if ifrom is None:
self.nodes[(jid, node)] = {'info': DiscoInfo(), ifrom = ''
if isinstance(ifrom, JID):
ifrom = ifrom.full
if (jid, node, ifrom) not in self.nodes:
self.nodes[(jid, node, ifrom)] = {'info': DiscoInfo(),
'items': DiscoItems()} 'items': DiscoItems()}
self.nodes[(jid, node)]['info']['node'] = node self.nodes[(jid, node, ifrom)]['info']['node'] = node
self.nodes[(jid, node)]['items']['node'] = node self.nodes[(jid, node, ifrom)]['items']['node'] = node
def get_node(self, jid=None, node=None, ifrom=None):
with self.lock:
if jid is None:
jid = self.xmpp.boundjid.full
if node is None:
node = ''
if ifrom is None:
ifrom = ''
if isinstance(ifrom, JID):
ifrom = ifrom.full
if (jid, node, ifrom) not in self.nodes:
self.add_node(jid, node, ifrom)
return self.nodes[(jid, node, ifrom)]
def node_exists(self, jid=None, node=None, ifrom=None):
with self.lock:
if jid is None:
jid = self.xmpp.boundjid.full
if node is None:
node = ''
if ifrom is None:
ifrom = ''
if isinstance(ifrom, JID):
ifrom = ifrom.full
if (jid, node, ifrom) not in self.nodes:
return False
return True
# ================================================================= # =================================================================
# Node Handlers # Node Handlers
# #
# Each handler accepts three arguments: jid, node, and data. # Each handler accepts four arguments: jid, node, ifrom, and data.
# The jid and node parameters together determine the set of # The jid and node parameters together determine the set of info
# info and items stanzas that will be retrieved or added. # and items stanzas that will be retrieved or added. Additionally,
# The data parameter is a dictionary with additional paramters # the ifrom value allows for cached results when results vary based
# that will be passed to other calls. # on the requester's JID. The data parameter is a dictionary with
# additional parameters that will be passed to other calls.
#
# This implementation does not allow different responses based on
# the requester's JID, except for cached results. To do that,
# register a custom node handler.
def get_info(self, jid, node, data): def get_info(self, jid, node, ifrom, data):
""" """
Return the stored info data for the requested JID/node combination. Return the stored info data for the requested JID/node combination.
The data parameter is not used. The data parameter is not used.
""" """
if (jid, node) not in self.nodes: with self.lock:
if not self.node_exists(jid, node):
if not node: if not node:
return DiscoInfo() return DiscoInfo()
else: else:
raise XMPPError(condition='item-not-found') raise XMPPError(condition='item-not-found')
else: else:
return self.nodes[(jid, node)]['info'] return self.get_node(jid, node)['info']
def del_info(self, jid, node, data): def del_info(self, jid, node, data):
""" """
@ -99,44 +140,48 @@ class StaticDisco(object):
The data parameter is not used. The data parameter is not used.
""" """
if (jid, node) in self.nodes: with self.lock:
self.nodes[(jid, node)]['info'] = DiscoInfo() if self.node_exists(jid, node):
self.get_node(jid, node)['info'] = DiscoInfo()
def get_items(self, jid, node, data): def get_items(self, jid, node, ifrom, data):
""" """
Return the stored items data for the requested JID/node combination. Return the stored items data for the requested JID/node combination.
The data parameter is not used. The data parameter is not used.
""" """
if (jid, node) not in self.nodes: with self.lock:
if not self.node_exists(jid, node):
if not node: if not node:
return DiscoInfo() return DiscoInfo()
else: else:
raise XMPPError(condition='item-not-found') raise XMPPError(condition='item-not-found')
else: else:
return self.nodes[(jid, node)]['items'] return self.get_node(jid, node)['items']
def set_items(self, jid, node, data): def set_items(self, jid, node, ifrom, data):
""" """
Replace the stored items data for a JID/node combination. Replace the stored items data for a JID/node combination.
The data parameter may provided: The data parameter may provided:
items -- A set of items in tuple format. items -- A set of items in tuple format.
""" """
with self.lock:
items = data.get('items', set()) items = data.get('items', set())
self.add_node(jid, node) self.add_node(jid, node)
self.nodes[(jid, node)]['items']['items'] = items self.get_node(jid, node)['items']['items'] = items
def del_items(self, jid, node, data): def del_items(self, jid, node, ifrom, data):
""" """
Reset the items stanza for a given JID/node combination. Reset the items stanza for a given JID/node combination.
The data parameter is not used. The data parameter is not used.
""" """
if (jid, node) in self.nodes: with self.lock:
self.nodes[(jid, node)]['items'] = DiscoItems() if self.node_exists(jid, node):
self.get_node(jid, node)['items'] = DiscoItems()
def add_identity(self, jid, node, data): def add_identity(self, jid, node, ifrom, data):
""" """
Add a new identity to te JID/node combination. Add a new identity to te JID/node combination.
@ -146,14 +191,15 @@ class StaticDisco(object):
name -- Optional human readable name for this identity. name -- Optional human readable name for this identity.
lang -- Optional standard xml:lang value. lang -- Optional standard xml:lang value.
""" """
with self.lock:
self.add_node(jid, node) self.add_node(jid, node)
self.nodes[(jid, node)]['info'].add_identity( self.get_node(jid, node)['info'].add_identity(
data.get('category', ''), data.get('category', ''),
data.get('itype', ''), data.get('itype', ''),
data.get('name', None), data.get('name', None),
data.get('lang', None)) data.get('lang', None))
def set_identities(self, jid, node, data): def set_identities(self, jid, node, ifrom, data):
""" """
Add or replace all identities for a JID/node combination. Add or replace all identities for a JID/node combination.
@ -161,11 +207,12 @@ class StaticDisco(object):
identities -- A list of identities in tuple form: identities -- A list of identities in tuple form:
(category, type, name, lang) (category, type, name, lang)
""" """
with self.lock:
identities = data.get('identities', set()) identities = data.get('identities', set())
self.add_node(jid, node) self.add_node(jid, node)
self.nodes[(jid, node)]['info']['identities'] = identities self.get_node(jid, node)['info']['identities'] = identities
def del_identity(self, jid, node, data): def del_identity(self, jid, node, ifrom, data):
""" """
Remove an identity from a JID/node combination. Remove an identity from a JID/node combination.
@ -175,67 +222,70 @@ class StaticDisco(object):
name -- Optional human readable name for this identity. name -- Optional human readable name for this identity.
lang -- Optional, standard xml:lang value. lang -- Optional, standard xml:lang value.
""" """
if (jid, node) not in self.nodes: with self.lock:
return if self.node_exists(jid, node):
self.nodes[(jid, node)]['info'].del_identity( self.get_node(jid, node)['info'].del_identity(
data.get('category', ''), data.get('category', ''),
data.get('itype', ''), data.get('itype', ''),
data.get('name', None), data.get('name', None),
data.get('lang', None)) data.get('lang', None))
def del_identities(self, jid, node, data): def del_identities(self, jid, node, ifrom, data):
""" """
Remove all identities from a JID/node combination. Remove all identities from a JID/node combination.
The data parameter is not used. The data parameter is not used.
""" """
if (jid, node) not in self.nodes: with self.lock:
return if self.node_exists(jid, node):
del self.nodes[(jid, node)]['info']['identities'] del self.get_node(jid, node)['info']['identities']
def add_feature(self, jid, node, data): def add_feature(self, jid, node, ifrom, data):
""" """
Add a feature to a JID/node combination. Add a feature to a JID/node combination.
The data parameter should include: The data parameter should include:
feature -- The namespace of the supported feature. feature -- The namespace of the supported feature.
""" """
with self.lock:
self.add_node(jid, node) self.add_node(jid, node)
self.nodes[(jid, node)]['info'].add_feature(data.get('feature', '')) self.get_node(jid, node)['info'].add_feature(data.get('feature', ''))
def set_features(self, jid, node, data): def set_features(self, jid, node, ifrom, data):
""" """
Add or replace all features for a JID/node combination. Add or replace all features for a JID/node combination.
The data parameter should include: The data parameter should include:
features -- The new set of supported features. features -- The new set of supported features.
""" """
with self.lock:
features = data.get('features', set()) features = data.get('features', set())
self.add_node(jid, node) self.add_node(jid, node)
self.nodes[(jid, node)]['info']['features'] = features self.get_node(jid, node)['info']['features'] = features
def del_feature(self, jid, node, data): def del_feature(self, jid, node, ifrom, data):
""" """
Remove a feature from a JID/node combination. Remove a feature from a JID/node combination.
The data parameter should include: The data parameter should include:
feature -- The namespace of the removed feature. feature -- The namespace of the removed feature.
""" """
if (jid, node) not in self.nodes: with self.lock:
return if self.node_exists(jid, node):
self.nodes[(jid, node)]['info'].del_feature(data.get('feature', '')) self.get_node(jid, node)['info'].del_feature(data.get('feature', ''))
def del_features(self, jid, node, data): def del_features(self, jid, node, ifrom, data):
""" """
Remove all features from a JID/node combination. Remove all features from a JID/node combination.
The data parameter is not used. The data parameter is not used.
""" """
if (jid, node) not in self.nodes: with self.lock:
if not self.node_exists(jid, node):
return return
del self.nodes[(jid, node)]['info']['features'] del self.get_node(jid, node)['info']['features']
def add_item(self, jid, node, data): def add_item(self, jid, node, ifrom, data):
""" """
Add an item to a JID/node combination. Add an item to a JID/node combination.
@ -245,13 +295,14 @@ class StaticDisco(object):
non-addressable items. non-addressable items.
name -- Optional human readable name for the item. name -- Optional human readable name for the item.
""" """
with self.lock:
self.add_node(jid, node) self.add_node(jid, node)
self.nodes[(jid, node)]['items'].add_item( self.get_node(jid, node)['items'].add_item(
data.get('ijid', ''), data.get('ijid', ''),
node=data.get('inode', ''), node=data.get('inode', ''),
name=data.get('name', '')) name=data.get('name', ''))
def del_item(self, jid, node, data): def del_item(self, jid, node, ifrom, data):
""" """
Remove an item from a JID/node combination. Remove an item from a JID/node combination.
@ -259,7 +310,35 @@ class StaticDisco(object):
ijid -- JID of the item to remove. ijid -- JID of the item to remove.
inode -- Optional extra identifying information. inode -- Optional extra identifying information.
""" """
if (jid, node) in self.nodes: with self.lock:
self.nodes[(jid, node)]['items'].del_item( if self.node_exists(jid, node):
self.get_node(jid, node)['items'].del_item(
data.get('ijid', ''), data.get('ijid', ''),
node=data.get('inode', None)) node=data.get('inode', None))
def cache_info(self, jid, node, ifrom, data):
"""
Cache disco information for an external JID.
The data parameter is the Iq result stanza
containing the disco info to cache, or
the disco#info substanza itself.
"""
with self.lock:
if isinstance(data, Iq):
data = data['disco_info']
self.add_node(jid, node, ifrom)
self.get_node(jid, node, ifrom)['info'] = data
def get_cached_info(self, jid, node, ifrom, data):
"""
Retrieve cached disco info data.
The data parameter is not used.
"""
with self.lock:
if not self.node_exists(jid, node, ifrom):
return None
else:
return self.get_node(jid, node, ifrom)['info']

View file

@ -76,7 +76,7 @@ class xep_0128(base_plugin):
as extended information, replacing any as extended information, replacing any
existing extensions. existing extensions.
""" """
self.disco._run_node_handler('set_extended_info', jid, node, kwargs) self.disco._run_node_handler('set_extended_info', jid, node, None, kwargs)
def add_extended_info(self, jid=None, node=None, **kwargs): def add_extended_info(self, jid=None, node=None, **kwargs):
""" """
@ -88,7 +88,7 @@ class xep_0128(base_plugin):
data -- Either a form, or a list of forms to add data -- Either a form, or a list of forms to add
as extended information. as extended information.
""" """
self.disco._run_node_handler('add_extended_info', jid, node, kwargs) self.disco._run_node_handler('add_extended_info', jid, node, None, kwargs)
def del_extended_info(self, jid=None, node=None, **kwargs): def del_extended_info(self, jid=None, node=None, **kwargs):
""" """
@ -98,4 +98,4 @@ class xep_0128(base_plugin):
jid -- The JID to modify. jid -- The JID to modify.
node -- The node to modify. node -- The node to modify.
""" """
self.disco._run_node_handler('del_extended_info', jid, node, kwargs) self.disco._run_node_handler('del_extended_info', jid, node, None, kwargs)

View file

@ -31,42 +31,43 @@ class StaticExtendedDisco(object):
""" """
self.static = static self.static = static
def set_extended_info(self, jid, node, data): def set_extended_info(self, jid, node, ifrom, data):
""" """
Replace the extended identity data for a JID/node combination. Replace the extended identity data for a JID/node combination.
The data parameter may provide: The data parameter may provide:
data -- Either a single data form, or a list of data forms. data -- Either a single data form, or a list of data forms.
""" """
self.del_extended_info(jid, node, data) with self.static.lock:
self.add_extended_info(jid, node, data) self.del_extended_info(jid, node, ifrom, data)
self.add_extended_info(jid, node, ifrom, data)
def add_extended_info(self, jid, node, data): def add_extended_info(self, jid, node, ifrom, data):
""" """
Add additional extended identity data for a JID/node combination. Add additional extended identity data for a JID/node combination.
The data parameter may provide: The data parameter may provide:
data -- Either a single data form, or a list of data forms. data -- Either a single data form, or a list of data forms.
""" """
with self.static.lock:
self.static.add_node(jid, node) self.static.add_node(jid, node)
forms = data.get('data', []) forms = data.get('data', [])
if not isinstance(forms, list): if not isinstance(forms, list):
forms = [forms] forms = [forms]
info = self.static.get_node(jid, node)['info']
for form in forms: for form in forms:
self.static.nodes[(jid, node)]['info'].append(form) info.append(form)
def del_extended_info(self, jid, node, data): def del_extended_info(self, jid, node, ifrom, data):
""" """
Replace the extended identity data for a JID/node combination. Replace the extended identity data for a JID/node combination.
The data parameter is not used. The data parameter is not used.
""" """
if (jid, node) not in self.static.nodes: with self.static.lock:
return if self.static.node_exists(jid, node):
info = self.static.get_node(jid, node)['info']
info = self.static.nodes[(jid, node)]['info']
for form in info['substanza']: for form in info['substanza']:
info.xml.remove(form.xml) info.xml.remove(form.xml)

View file

@ -122,7 +122,7 @@ class TestStreamDisco(SleekTest):
self.stream_start(mode='client', self.stream_start(mode='client',
plugins=['xep_0030']) plugins=['xep_0030'])
def dynamic_jid(jid, node, iq): def dynamic_jid(jid, node, ifrom, iq):
result = self.xmpp['xep_0030'].stanza.DiscoInfo() result = self.xmpp['xep_0030'].stanza.DiscoInfo()
result['node'] = node result['node'] = node
result.add_identity('client', 'console', name='Dynamic Info') result.add_identity('client', 'console', name='Dynamic Info')
@ -158,7 +158,7 @@ class TestStreamDisco(SleekTest):
jid='tester.localhost', jid='tester.localhost',
plugins=['xep_0030']) plugins=['xep_0030'])
def dynamic_global(jid, node, iq): def dynamic_global(jid, node, ifrom, iq):
result = self.xmpp['xep_0030'].stanza.DiscoInfo() result = self.xmpp['xep_0030'].stanza.DiscoInfo()
result['node'] = node result['node'] = node
result.add_identity('component', 'generic', name='Dynamic Info') result.add_identity('component', 'generic', name='Dynamic Info')
@ -194,7 +194,7 @@ class TestStreamDisco(SleekTest):
self.stream_start(mode='client', self.stream_start(mode='client',
plugins=['xep_0030']) plugins=['xep_0030'])
def dynamic_jid(jid, node, iq): def dynamic_jid(jid, node, ifrom, iq):
result = self.xmpp['xep_0030'].stanza.DiscoInfo() result = self.xmpp['xep_0030'].stanza.DiscoInfo()
result['node'] = node result['node'] = node
result.add_identity('client', 'console', name='Dynamic Info') result.add_identity('client', 'console', name='Dynamic Info')
@ -236,7 +236,7 @@ class TestStreamDisco(SleekTest):
jid='tester.localhost', jid='tester.localhost',
plugins=['xep_0030']) plugins=['xep_0030'])
def dynamic_global(jid, node, iq): def dynamic_global(jid, node, ifrom, iq):
result = self.xmpp['xep_0030'].stanza.DiscoInfo() result = self.xmpp['xep_0030'].stanza.DiscoInfo()
result['node'] = node result['node'] = node
result.add_identity('component', 'generic', name='Dynamic Info') result.add_identity('component', 'generic', name='Dynamic Info')
@ -325,7 +325,7 @@ class TestStreamDisco(SleekTest):
self.stream_start(mode='client', self.stream_start(mode='client',
plugins=['xep_0030']) plugins=['xep_0030'])
def dynamic_jid(jid, node, iq): def dynamic_jid(jid, node, ifrom, iq):
result = self.xmpp['xep_0030'].stanza.DiscoItems() result = self.xmpp['xep_0030'].stanza.DiscoItems()
result['node'] = node result['node'] = node
result.add_item('tester@localhost', node='foo', name='JID') result.add_item('tester@localhost', node='foo', name='JID')
@ -359,7 +359,7 @@ class TestStreamDisco(SleekTest):
jid='tester.localhost', jid='tester.localhost',
plugins=['xep_0030']) plugins=['xep_0030'])
def dynamic_global(jid, node, iq): def dynamic_global(jid, node, ifrom, iq):
result = self.xmpp['xep_0030'].stanza.DiscoItems() result = self.xmpp['xep_0030'].stanza.DiscoItems()
result['node'] = node result['node'] = node
result.add_item('tester@localhost', node='foo', name='Global') result.add_item('tester@localhost', node='foo', name='Global')
@ -393,7 +393,7 @@ class TestStreamDisco(SleekTest):
self.stream_start(mode='client', self.stream_start(mode='client',
plugins=['xep_0030']) plugins=['xep_0030'])
def dynamic_jid(jid, node, iq): def dynamic_jid(jid, node, ifrom, iq):
result = self.xmpp['xep_0030'].stanza.DiscoItems() result = self.xmpp['xep_0030'].stanza.DiscoItems()
result['node'] = node result['node'] = node
result.add_item('tester@localhost', node='foo', name='Global') result.add_item('tester@localhost', node='foo', name='Global')
@ -435,7 +435,7 @@ class TestStreamDisco(SleekTest):
jid='tester.localhost', jid='tester.localhost',
plugins=['xep_0030']) plugins=['xep_0030'])
def dynamic_global(jid, node, iq): def dynamic_global(jid, node, ifrom, iq):
result = self.xmpp['xep_0030'].stanza.DiscoItems() result = self.xmpp['xep_0030'].stanza.DiscoItems()
result['node'] = node result['node'] = node
result.add_item('tester.localhost', node='foo', name='Global') result.add_item('tester.localhost', node='foo', name='Global')