diff options
author | Lance Stout <lancestout@gmail.com> | 2012-01-05 11:33:47 -0500 |
---|---|---|
committer | Lance Stout <lancestout@gmail.com> | 2012-01-05 11:33:47 -0500 |
commit | 8fd2efa2fa50751af7b5182ad7793295c540d294 (patch) | |
tree | 7639768de4d93b00e998f3575b02830b2ac97b44 /sleekxmpp/plugins/xep_0030 | |
parent | 79f1aa0e1ba7dd29bf597beeae924b96950f9416 (diff) | |
parent | 6b6995bb0b80c91eda72bc92974f68133cef93a3 (diff) | |
download | slixmpp-8fd2efa2fa50751af7b5182ad7793295c540d294.tar.gz slixmpp-8fd2efa2fa50751af7b5182ad7793295c540d294.tar.bz2 slixmpp-8fd2efa2fa50751af7b5182ad7793295c540d294.tar.xz slixmpp-8fd2efa2fa50751af7b5182ad7793295c540d294.zip |
Merge branch 'develop-1.1' into develop
Diffstat (limited to 'sleekxmpp/plugins/xep_0030')
-rw-r--r-- | sleekxmpp/plugins/xep_0030/disco.py | 246 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0030/stanza/info.py | 36 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0030/static.py | 354 |
3 files changed, 496 insertions, 140 deletions
diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py index 53086d4e..10f9ef4e 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._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'] + self.use_cache = self.config.get('use_cache', True) + self.wrap_results = self.config.get('wrap_results', False) + + self._disco_ops = [ + 'get_info', 'set_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', 'cache_info', + 'get_cached_info', 'supports', 'has_identity'] + self.default_handlers = {} self._handlers = {} for op in self._disco_ops: @@ -237,7 +242,78 @@ 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. + """ + data = {'feature': feature, + 'local': local, + 'cached': cached} + return self._run_node_handler('supports', jid, node, ifrom, data) + + def has_identity(self, jid=None, node=None, category=None, itype=None, + lang=None, local=False, cached=True, ifrom=None): + """ + Check if a JID provides a given identity. + + Return values: + True -- The identity is provided + False -- The identity is not listed + None -- Nothing could be found due to a timeout + + Arguments: + jid -- Request info from this JID. + node -- The particular node to query. + category -- The category of the identity to check. + itype -- The type of the identity to check. + lang -- The language of the identity 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. + """ + data = {'category': category, + 'itype': itype, + 'lang': lang, + 'local': local, + 'cached': cached} + return self._run_node_handler('has_identity', jid, node, ifrom, data) + + 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 +333,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 @@ -266,12 +349,31 @@ class xep_0030(base_plugin): received instead of blocking and waiting for the reply. """ - if local or jid is None: + if jid is not None and not isinstance(jid, JID): + jid = JID(jid) + if self.xmpp.is_component: + if jid.domain == self.xmpp.boundjid.domain: + local = True + else: + if str(jid) == str(self.xmpp.boundjid): + local = True + + if local or jid in (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', '')) @@ -282,6 +384,15 @@ class xep_0030(base_plugin): block=kwargs.get('block', True), callback=kwargs.get('callback', None)) + def set_info(self, jid=None, node=None, info=None): + """ + Set the disco#info data for a JID/node based on an existing + disco#info stanza. + """ + if isinstance(info, Iq): + info = info['disco_info'] + self._run_node_handler('set_info', jid, node, None, info) + def get_items(self, jid=None, node=None, local=False, **kwargs): """ Retrieve the disco#items results from a given JID/node combination. @@ -314,7 +425,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 +454,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 +464,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 +485,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 +497,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 +524,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 +536,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 +550,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 +561,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 +576,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 +591,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 +603,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 +613,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=None, ifrom=None, data={}): """ Execute the most specific node handler for the given JID/node combination. @@ -513,7 +626,7 @@ class xep_0030(base_plugin): node -- The node requested. data -- Optional, custom data to pass to the handler. """ - if jid is None: + if jid in (None, ''): if self.xmpp.is_component: jid = self.xmpp.boundjid.full else: @@ -521,14 +634,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 +677,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 +688,20 @@ 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']) + if self.xmpp.is_component: + ito = iq['to'].full + else: + ito = None + self._run_node_handler('cache_info', + iq['from'].full, + iq['disco_info']['node'], + ito, + iq) self.xmpp.event('disco_info', iq) def _handle_disco_items(self, iq): @@ -583,6 +723,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() @@ -592,7 +733,7 @@ class xep_0030(base_plugin): iq.set_payload(items.xml) iq.send() elif iq['type'] == 'result': - log.debug("Received disco items result from" + \ + log.debug("Received disco items result from " + \ "%s to %s.", iq['from'], iq['to']) self.xmpp.event('disco_items', iq) @@ -607,21 +748,46 @@ 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: - log.debug("No identity found for this entity." + \ + log.debug("No identity found for this entity. " + \ "Using default component identity.") info.add_identity('component', 'generic') else: - log.debug("No identity found for this entity." + \ + log.debug("No identity found for this entity. " + \ "Using default client identity.") info.add_identity('client', 'bot') if not info['features']: - log.debug("No features found for this entity." + \ + 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 diff --git a/sleekxmpp/plugins/xep_0030/stanza/info.py b/sleekxmpp/plugins/xep_0030/stanza/info.py index 6764acbb..25d1d07f 100644 --- a/sleekxmpp/plugins/xep_0030/stanza/info.py +++ b/sleekxmpp/plugins/xep_0030/stanza/info.py @@ -146,7 +146,7 @@ class DiscoInfo(ElementBase): return True return False - def get_identities(self, lang=None): + def get_identities(self, lang=None, dedupe=True): """ Return a set of all identities in tuple form as so: (category, type, lang, name) @@ -155,17 +155,25 @@ class DiscoInfo(ElementBase): that language. Arguments: - lang -- Optional, standard xml:lang value. + lang -- Optional, standard xml:lang value. + dedupe -- If True, de-duplicate identities, otherwise + return a list of all identities. """ - identities = set() + if dedupe: + identities = set() + else: + identities = [] for id_xml in self.findall('{%s}identity' % self.namespace): xml_lang = id_xml.attrib.get('{%s}lang' % self.xml_ns, None) if lang is None or xml_lang == lang: - identities.add(( - id_xml.attrib['category'], - id_xml.attrib['type'], - id_xml.attrib.get('{%s}lang' % self.xml_ns, None), - id_xml.attrib.get('name', None))) + id = (id_xml.attrib['category'], + id_xml.attrib['type'], + id_xml.attrib.get('{%s}lang' % self.xml_ns, None), + id_xml.attrib.get('name', None)) + if dedupe: + identities.add(id) + else: + identities.append(id) return identities def set_identities(self, identities, lang=None): @@ -237,11 +245,17 @@ class DiscoInfo(ElementBase): return True return False - def get_features(self): + def get_features(self, dedupe=True): """Return the set of all supported features.""" - features = set() + if dedupe: + features = set() + else: + features = [] for feature_xml in self.findall('{%s}feature' % self.namespace): - features.add(feature_xml.attrib['var']) + if dedupe: + features.add(feature_xml.attrib['var']) + else: + features.append(feature_xml.attrib['var']) return features def set_features(self, features): diff --git a/sleekxmpp/plugins/xep_0030/static.py b/sleekxmpp/plugins/xep_0030/static.py index 7e7f0353..0b196b40 100644 --- a/sleekxmpp/plugins/xep_0030/static.py +++ b/sleekxmpp/plugins/xep_0030/static.py @@ -7,6 +7,7 @@ """ import logging +import threading import sleekxmpp from sleekxmpp import Iq @@ -50,8 +51,10 @@ class StaticDisco(object): """ self.nodes = {} self.xmpp = xmpp + self.disco = xmpp['xep_0030'] + 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 JID and node combination. @@ -60,83 +63,219 @@ class StaticDisco(object): jid -- The JID that will own the new stanzas. node -- The node that will own the new stanzas. """ - if jid is None: - jid = self.xmpp.boundjid.full - if node is None: - node = '' - if (jid, node) not in self.nodes: - self.nodes[(jid, node)] = {'info': DiscoInfo(), - 'items': DiscoItems()} - self.nodes[(jid, node)]['info']['node'] = node - self.nodes[(jid, node)]['items']['node'] = node + 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.nodes[(jid, node, ifrom)] = {'info': DiscoInfo(), + 'items': DiscoItems()} + self.nodes[(jid, node, ifrom)]['info']['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 # - # Each handler accepts three arguments: jid, node, and data. - # The jid and node parameters together determine the set of - # info and items stanzas that will be retrieved or added. - # The data parameter is a dictionary with additional paramters - # that will be passed to other calls. + # Each handler accepts four arguments: jid, node, ifrom, and data. + # The jid and node parameters together determine the set of info + # and items stanzas that will be retrieved or added. Additionally, + # the ifrom value allows for cached results when results vary based + # 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 supports(self, jid, node, ifrom, data): + """ + Check if a JID supports a given feature. + + The data parameter may provide: + feature -- The feature to check for support. + 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. + """ + feature = data.get('feature', None) + + data = {'local': data.get('local', False), + 'cached': data.get('cached', True)} + + if not feature: + return False + + try: + info = self.disco.get_info(jid=jid, node=node, + ifrom=ifrom, **data) + info = self.disco._wrap(ifrom, jid, info, True) + features = info['disco_info']['features'] + return feature in features + except IqError: + return False + except IqTimeout: + return None + + def has_identity(self, jid, node, ifrom, data): + """ + Check if a JID has a given identity. + + The data parameter may provide: + category -- The category of the identity to check. + itype -- The type of the identity to check. + lang -- The language of the identity 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. + """ + identity = (data.get('category', None), + data.get('itype', None), + data.get('lang', None)) + + data = {'local': data.get('local', False), + 'cached': data.get('cached', True)} + + if node in (None, ''): + info = self.caps.get_caps(jid) + if info and identity in info['identities']: + return True + + try: + info = self.disco.get_info(jid=jid, node=node, + ifrom=ifrom, **data) + info = self.disco._wrap(ifrom, jid, info, True) + trunc = lambda i: (i[0], i[1], i[2]) + return identity in map(trunc, info['disco_info']['identities']) + except IqError: + return False + except IqTimeout: + return None + + + def get_info(self, jid, node, ifrom, data): """ Return the stored info data for the requested JID/node combination. The data parameter is not used. """ - if (jid, node) not in self.nodes: - if not node: - return DiscoInfo() + with self.lock: + if not self.node_exists(jid, node): + if not node: + return DiscoInfo() + else: + raise XMPPError(condition='item-not-found') else: - raise XMPPError(condition='item-not-found') - else: - return self.nodes[(jid, node)]['info'] + return self.get_node(jid, node)['info'] - def del_info(self, jid, node, data): + def set_info(self, jid, node, ifrom, data): + """ + Set the entire info stanza for a JID/node at once. + + The data parameter is a disco#info substanza. + """ + with self.lock: + self.add_node(jid, node) + self.get_node(jid, node)['info'] = data + + def del_info(self, jid, node, ifrom, data): """ Reset the info stanza for a given JID/node combination. The data parameter is not used. """ - if (jid, node) in self.nodes: - self.nodes[(jid, node)]['info'] = DiscoInfo() + with self.lock: + 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. The data parameter is not used. """ - if (jid, node) not in self.nodes: - if not node: - return DiscoInfo() + with self.lock: + if not self.node_exists(jid, node): + if not node: + return DiscoInfo() + else: + raise XMPPError(condition='item-not-found') else: - raise XMPPError(condition='item-not-found') - 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. - The data parameter may provided: + The data parameter may provide: items -- A set of items in tuple format. """ - items = data.get('items', set()) - self.add_node(jid, node) - self.nodes[(jid, node)]['items']['items'] = items + with self.lock: + items = data.get('items', set()) + self.add_node(jid, node) + 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. The data parameter is not used. """ - if (jid, node) in self.nodes: - self.nodes[(jid, node)]['items'] = DiscoItems() + with self.lock: + 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. @@ -146,14 +285,15 @@ class StaticDisco(object): name -- Optional human readable name for this identity. lang -- Optional standard xml:lang value. """ - self.add_node(jid, node) - self.nodes[(jid, node)]['info'].add_identity( - data.get('category', ''), - data.get('itype', ''), - data.get('name', None), - data.get('lang', None)) + with self.lock: + self.add_node(jid, node) + self.get_node(jid, node)['info'].add_identity( + data.get('category', ''), + data.get('itype', ''), + data.get('name', 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. @@ -161,11 +301,12 @@ class StaticDisco(object): identities -- A list of identities in tuple form: (category, type, name, lang) """ - identities = data.get('identities', set()) - self.add_node(jid, node) - self.nodes[(jid, node)]['info']['identities'] = identities + with self.lock: + identities = data.get('identities', set()) + self.add_node(jid, node) + 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. @@ -175,67 +316,70 @@ class StaticDisco(object): name -- Optional human readable name for this identity. lang -- Optional, standard xml:lang value. """ - if (jid, node) not in self.nodes: - return - self.nodes[(jid, node)]['info'].del_identity( - data.get('category', ''), - data.get('itype', ''), - data.get('name', None), - data.get('lang', None)) + with self.lock: + if self.node_exists(jid, node): + self.get_node(jid, node)['info'].del_identity( + data.get('category', ''), + data.get('itype', ''), + data.get('name', 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. The data parameter is not used. """ - if (jid, node) not in self.nodes: - return - del self.nodes[(jid, node)]['info']['identities'] + with self.lock: + if self.node_exists(jid, node): + 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. The data parameter should include: feature -- The namespace of the supported feature. """ - self.add_node(jid, node) - self.nodes[(jid, node)]['info'].add_feature(data.get('feature', '')) + with self.lock: + self.add_node(jid, node) + 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. The data parameter should include: features -- The new set of supported features. """ - features = data.get('features', set()) - self.add_node(jid, node) - self.nodes[(jid, node)]['info']['features'] = features + with self.lock: + features = data.get('features', set()) + self.add_node(jid, node) + 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. The data parameter should include: feature -- The namespace of the removed feature. """ - if (jid, node) not in self.nodes: - return - self.nodes[(jid, node)]['info'].del_feature(data.get('feature', '')) + with self.lock: + if self.node_exists(jid, node): + 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. The data parameter is not used. """ - if (jid, node) not in self.nodes: - return - del self.nodes[(jid, node)]['info']['features'] + with self.lock: + if not self.node_exists(jid, node): + return + 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. @@ -245,13 +389,14 @@ class StaticDisco(object): non-addressable items. name -- Optional human readable name for the item. """ - self.add_node(jid, node) - self.nodes[(jid, node)]['items'].add_item( - data.get('ijid', ''), - node=data.get('inode', ''), - name=data.get('name', '')) + with self.lock: + self.add_node(jid, node) + self.get_node(jid, node)['items'].add_item( + data.get('ijid', ''), + node=data.get('inode', ''), + 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. @@ -259,7 +404,38 @@ class StaticDisco(object): ijid -- JID of the item to remove. inode -- Optional extra identifying information. """ - if (jid, node) in self.nodes: - self.nodes[(jid, node)]['items'].del_item( - data.get('ijid', ''), - node=data.get('inode', None)) + with self.lock: + if self.node_exists(jid, node): + self.get_node(jid, node)['items'].del_item( + data.get('ijid', ''), + 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 isinstance(jid, JID): + jid = jid.full + + if not self.node_exists(jid, node, ifrom): + return None + else: + return self.get_node(jid, node, ifrom)['info'] |