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:
@@ -10,7 +10,7 @@ import logging
|
||||
|
||||
import sleekxmpp
|
||||
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.xmlstream.handler import Callback
|
||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||
@@ -108,11 +108,16 @@ class xep_0030(base_plugin):
|
||||
|
||||
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',
|
||||
'get_items', 'set_items', 'del_items',
|
||||
'add_identity', 'del_identity', 'add_feature',
|
||||
'del_feature', 'add_item', 'del_item',
|
||||
'del_identities', 'del_features']
|
||||
'del_identities', 'del_features',
|
||||
'cache_info', 'get_cached_info']
|
||||
|
||||
self.default_handlers = {}
|
||||
self._handlers = {}
|
||||
for op in self._disco_ops:
|
||||
@@ -237,7 +242,47 @@ class xep_0030(base_plugin):
|
||||
self.del_node_handler(op, jid, node)
|
||||
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.
|
||||
|
||||
@@ -257,6 +302,13 @@ class xep_0030(base_plugin):
|
||||
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.
|
||||
block -- If true, block and wait for the stanzas' reply.
|
||||
timeout -- The time in seconds to block while waiting for
|
||||
@@ -269,9 +321,19 @@ class xep_0030(base_plugin):
|
||||
if local or jid is None:
|
||||
log.debug("Looking up local disco#info data " + \
|
||||
"for %s, node %s.", jid, node)
|
||||
info = self._run_node_handler('get_info', jid, node, kwargs)
|
||||
return self._fix_default_info(info)
|
||||
info = self._run_node_handler('get_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()
|
||||
# Check dfrom parameter for backwards compatibility
|
||||
iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', ''))
|
||||
@@ -314,7 +376,9 @@ class xep_0030(base_plugin):
|
||||
Otherwise the parameter is ignored.
|
||||
"""
|
||||
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()
|
||||
# Check dfrom parameter for backwards compatibility
|
||||
@@ -341,7 +405,7 @@ class xep_0030(base_plugin):
|
||||
node -- Optional node to modify.
|
||||
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):
|
||||
"""
|
||||
@@ -351,7 +415,7 @@ class xep_0030(base_plugin):
|
||||
jid -- The JID 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):
|
||||
"""
|
||||
@@ -372,7 +436,7 @@ class xep_0030(base_plugin):
|
||||
kwargs = {'ijid': jid,
|
||||
'name': name,
|
||||
'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):
|
||||
"""
|
||||
@@ -384,7 +448,7 @@ class xep_0030(base_plugin):
|
||||
ijid -- The item's JID.
|
||||
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='',
|
||||
node=None, jid=None, lang=None):
|
||||
@@ -411,7 +475,7 @@ class xep_0030(base_plugin):
|
||||
'itype': itype,
|
||||
'name': name,
|
||||
'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):
|
||||
"""
|
||||
@@ -423,7 +487,7 @@ class xep_0030(base_plugin):
|
||||
jid -- The JID to modify.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
@@ -437,7 +501,7 @@ class xep_0030(base_plugin):
|
||||
name -- Optional, human readable name for the identity.
|
||||
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):
|
||||
"""
|
||||
@@ -448,7 +512,7 @@ class xep_0030(base_plugin):
|
||||
node -- The node to modify.
|
||||
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):
|
||||
"""
|
||||
@@ -463,7 +527,7 @@ class xep_0030(base_plugin):
|
||||
identities -- A set of identities in tuple form.
|
||||
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):
|
||||
"""
|
||||
@@ -478,7 +542,7 @@ class xep_0030(base_plugin):
|
||||
lang -- Optional. If given, only remove identities
|
||||
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):
|
||||
"""
|
||||
@@ -490,7 +554,7 @@ class xep_0030(base_plugin):
|
||||
node -- The node to modify.
|
||||
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):
|
||||
"""
|
||||
@@ -500,9 +564,9 @@ class xep_0030(base_plugin):
|
||||
jid -- The JID 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
|
||||
JID/node combination.
|
||||
@@ -521,14 +585,28 @@ class xep_0030(base_plugin):
|
||||
if node is None:
|
||||
node = ''
|
||||
|
||||
if self._handlers[htype]['node'].get((jid, node), False):
|
||||
return self._handlers[htype]['node'][(jid, node)](jid, node, data)
|
||||
elif self._handlers[htype]['jid'].get(jid, False):
|
||||
return self._handlers[htype]['jid'][jid](jid, node, data)
|
||||
elif self._handlers[htype]['global']:
|
||||
return self._handlers[htype]['global'](jid, node, data)
|
||||
else:
|
||||
return None
|
||||
try:
|
||||
args = (jid, node, ifrom, 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:
|
||||
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:
|
||||
return None
|
||||
|
||||
def _handle_disco_info(self, iq):
|
||||
"""
|
||||
@@ -550,6 +628,7 @@ class xep_0030(base_plugin):
|
||||
info = self._run_node_handler('get_info',
|
||||
jid,
|
||||
iq['disco_info']['node'],
|
||||
iq['from'],
|
||||
iq)
|
||||
if isinstance(info, Iq):
|
||||
info.send()
|
||||
@@ -560,8 +639,16 @@ class xep_0030(base_plugin):
|
||||
iq.set_payload(info.xml)
|
||||
iq.send()
|
||||
elif iq['type'] == 'result':
|
||||
log.debug("Received disco info result from" + \
|
||||
"%s to %s.", iq['from'], iq['to'])
|
||||
log.debug("Received disco info result from " + \
|
||||
"<%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)
|
||||
|
||||
def _handle_disco_items(self, iq):
|
||||
@@ -583,6 +670,7 @@ class xep_0030(base_plugin):
|
||||
items = self._run_node_handler('get_items',
|
||||
jid,
|
||||
iq['disco_items']['node'],
|
||||
iq['from'].full,
|
||||
iq)
|
||||
if isinstance(items, Iq):
|
||||
items.send()
|
||||
@@ -607,6 +695,9 @@ class xep_0030(base_plugin):
|
||||
Arguments:
|
||||
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['identities']:
|
||||
if self.xmpp.is_component:
|
||||
@@ -621,7 +712,29 @@ class xep_0030(base_plugin):
|
||||
log.debug("No features found for this entity." + \
|
||||
"Using default disco#info feature.")
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user