From d979b5f2b9cb38bdd145c95526358238449fa067 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 28 Dec 2011 10:07:33 -0500 Subject: 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. --- sleekxmpp/plugins/xep_0030/disco.py | 173 +++++++++++++++++++++++++++++------- 1 file changed, 143 insertions(+), 30 deletions(-) (limited to 'sleekxmpp/plugins/xep_0030/disco.py') diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py index 53086d4e..503631ec 100644 --- a/sleekxmpp/plugins/xep_0030/disco.py +++ b/sleekxmpp/plugins/xep_0030/disco.py @@ -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 -- cgit v1.2.3