summaryrefslogtreecommitdiff
path: root/sleekxmpp/plugins/xep_0030
diff options
context:
space:
mode:
authorLance Stout <lancestout@gmail.com>2010-12-16 22:03:56 -0500
committerLance Stout <lancestout@gmail.com>2010-12-16 22:03:56 -0500
commitadade2e5eccf5a0c48b0b6541fc3d990d732710c (patch)
tree9d5bd167058070bfe2b90a7a093e53978a4e29ed /sleekxmpp/plugins/xep_0030
parentc16913c99929a6a5a57611ec8a1757e3e82d4a45 (diff)
parentcbc42c29fb02a6fd22a0c303e8d02364545c86cf (diff)
downloadslixmpp-adade2e5eccf5a0c48b0b6541fc3d990d732710c.tar.gz
slixmpp-adade2e5eccf5a0c48b0b6541fc3d990d732710c.tar.bz2
slixmpp-adade2e5eccf5a0c48b0b6541fc3d990d732710c.tar.xz
slixmpp-adade2e5eccf5a0c48b0b6541fc3d990d732710c.zip
Merge branch 'develop' into roster
Diffstat (limited to 'sleekxmpp/plugins/xep_0030')
-rw-r--r--sleekxmpp/plugins/xep_0030/disco.py366
-rw-r--r--sleekxmpp/plugins/xep_0030/stanza/disco.py0
-rw-r--r--sleekxmpp/plugins/xep_0030/stanza/items.py4
-rw-r--r--sleekxmpp/plugins/xep_0030/static.py49
4 files changed, 335 insertions, 84 deletions
diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py
index c323ba7c..ad3d0ae2 100644
--- a/sleekxmpp/plugins/xep_0030/disco.py
+++ b/sleekxmpp/plugins/xep_0030/disco.py
@@ -26,30 +26,66 @@ class xep_0030(base_plugin):
"""
XEP-0030: Service Discovery
+ Service discovery in XMPP allows entities to discover information about
+ other agents in the network, such as the feature sets supported by a
+ client, or signposts to other, related entities.
+
+ Also see <http://www.xmpp.org/extensions/xep-0030.html>.
+
+ The XEP-0030 plugin works using a hierarchy of dynamic
+ node handlers, ranging from global handlers to specific
+ JID+node handlers. The default set of handlers operate
+ in a static manner, storing disco information in memory.
+ However, custom handlers may use any available backend
+ storage mechanism desired, such as SQLite or Redis.
+
+ Node handler hierarchy:
+ JID | Node | Level
+ ---------------------
+ None | None | Global
+ Given | None | All nodes for the JID
+ None | Given | Node on self.xmpp.boundjid
+ Given | Given | A single node
+
Stream Handlers:
- Disco Info --
- Disco Items --
+ Disco Info -- Any Iq stanze that includes a query with the
+ namespace http://jabber.org/protocol/disco#info.
+ Disco Items -- Any Iq stanze that includes a query with the
+ namespace http://jabber.org/protocol/disco#items.
Events:
- disco_info --
- disco_items --
- disco_info_query --
- disco_items_query --
+ disco_info -- Received a disco#info Iq query result.
+ disco_items -- Received a disco#items Iq query result.
+ disco_info_query -- Received a disco#info Iq query request.
+ disco_items_query -- Received a disco#items Iq query request.
+
+ Attributes:
+ stanza -- A reference to the module containing the stanza classes
+ provided by this plugin.
+ static -- Object containing the default set of static node handlers.
+ xmpp -- The main SleekXMPP object.
Methods:
- set_node_handler --
- del_node_handler --
- add_identity --
+ set_node_handler -- Assign a handler to a JID/node combination.
+ del_node_handler -- Remove a handler from a JID/node combination.
+ get_info -- Retrieve disco#info data, locally or remote.
+ get_items -- Retrieve disco#items data, locally or remote.
+ set_identities --
+ set_features --
+ set_items --
+ del_items --
del_identity --
- add_feature --
del_feature --
- add_item --
del_item --
- get_info --
- get_items --
+ add_identity --
+ add_feature --
+ add_item --
"""
def plugin_init(self):
+ """
+ Start the XEP-0030 plugin.
+ """
self.xep = '0030'
self.description = 'Service Discovery'
self.stanza = sleekxmpp.plugins.xep_0030.stanza
@@ -70,42 +106,89 @@ class xep_0030(base_plugin):
self.static = StaticDisco(self.xmpp)
self._disco_ops = ['get_info', 'set_identities', 'set_features',
- 'del_info', 'get_items', 'set_items', 'del_items',
+ 'get_items', 'set_items', 'del_items',
'add_identity', 'del_identity', 'add_feature',
'del_feature', 'add_item', 'del_item']
- self.handlers = {}
+ self._handlers = {}
for op in self._disco_ops:
- self.handlers[op] = {'global': getattr(self.static, op),
- 'jid': {},
- 'node': {}}
-
+ self._handlers[op] = {'global': getattr(self.static, op),
+ 'jid': {},
+ 'node': {}}
def set_node_handler(self, htype, jid=None, node=None, handler=None):
"""
+ Add a node handler for the given hierarchy level and
+ handler type.
+
+ Node handlers are ordered in a hierarchy where the
+ most specific handler is executed. Thus, a fallback,
+ global handler can be used for the majority of cases
+ with a few node specific handler that override the
+ global behavior.
+
+ Node handler hierarchy:
+ JID | Node | Level
+ ---------------------
+ None | None | Global
+ Given | None | All nodes for the JID
+ None | Given | Node on self.xmpp.boundjid
+ Given | Given | A single node
+
+ Handler types:
+ get_info
+ get_items
+ set_identities
+ set_features
+ set_items
+ del_items
+ del_identity
+ del_feature
+ del_item
+ add_identity
+ add_feature
+ add_item
+
Arguments:
- htype
- jid
- node
- handler
+ htype -- The operation provided by the handler.
+ jid -- The JID the handler applies to. May be narrowed
+ further if a node is given.
+ node -- The particular node the handler is for. If no JID
+ is given, then the self.xmpp.boundjid.full is
+ assumed.
+ handler -- The handler function to use.
"""
if htype not in self._disco_ops:
return
if jid is None and node is None:
- self.handlers[htype]['global'] = handler
+ self._handlers[htype]['global'] = handler
elif node is None:
- self.handlers[htype]['jid'][jid] = handler
+ self._handlers[htype]['jid'][jid] = handler
elif jid is None:
jid = self.xmpp.boundjid.full
- self.handlers[htype]['node'][(jid, node)] = handler
+ self._handlers[htype]['node'][(jid, node)] = handler
else:
- self.handlers[htype]['node'][(jid, node)] = handler
+ self._handlers[htype]['node'][(jid, node)] = handler
def del_node_handler(self, htype, jid, node):
"""
+ Remove a handler type for a JID and node combination.
+
+ The next handler in the hierarchy will be used if one
+ exists. If removing the global handler, make sure that
+ other handlers exist to process existing nodes.
+
+ Node handler hierarchy:
+ JID | Node | Level
+ ---------------------
+ None | None | Global
+ Given | None | All nodes for the JID
+ None | Given | Node on self.xmpp.boundjid
+ Given | Given | A single node
+
Arguments:
- htype
- jid
- node
+ htype -- The type of handler to remove.
+ jid -- The JID from which to remove the handler.
+ node -- The node from which to remove the handler.
"""
self.set_node_handler(htype, jid, node, None)
@@ -132,14 +215,28 @@ class xep_0030(base_plugin):
def get_info(self, jid=None, node=None, local=False, **kwargs):
"""
+ Retrieve the disco#info results from a given JID/node combination.
+
+ Info may be retrieved from both local resources and remote agents;
+ the local parameter indicates if the information should be gathered
+ by executing the local node handlers, or if a disco#info stanza
+ must be generated and sent.
+
Arguments:
- jid --
- node --
- local --
- dfrom --
- block --
- timeout --
- callback --
+ jid -- Request info from this JID.
+ node -- The particular node to query.
+ 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.
+ dfrom -- 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
+ a reply. If None, then wait indefinitely.
+ callback -- Optional callback to execute when a reply is
+ received instead of blocking and waiting for
+ the reply.
"""
if local or jid is None:
log.debug("Looking up local disco#info data " + \
@@ -158,14 +255,28 @@ class xep_0030(base_plugin):
def get_items(self, jid=None, node=None, local=False, **kwargs):
"""
+ Retrieve the disco#items results from a given JID/node combination.
+
+ Items may be retrieved from both local resources and remote agents;
+ the local parameter indicates if the items should be gathered by
+ executing the local node handlers, or if a disco#items stanza must
+ be generated and sent.
+
Arguments:
- jid --
- node --
- local --
- dfrom --
- block --
- timeout --
- callback --
+ jid -- Request info from this JID.
+ node -- The particular node to query.
+ 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 items.
+ dfrom -- 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
+ a reply. If None, then wait indefinitely.
+ callback -- Optional callback to execute when a reply is
+ received instead of blocking and waiting for
+ the reply.
"""
if local or jid is None:
return self._run_node_handler('get_items', jid, node, kwargs)
@@ -179,37 +290,169 @@ class xep_0030(base_plugin):
block=kwargs.get('block', None),
callback=kwargs.get('callback', None))
- def set_info(self, jid=None, node=None, **kwargs):
- self._run_node_handler('set_info', jid, node, kwargs)
+ def set_items(self, jid=None, node=None, **kwargs):
+ """
+ Set or replace all items for the specified JID/node combination.
- def del_info(self, jid=None, node=None, **kwargs):
- self._run_node_handler('del_info', jid, node, kwargs)
+ The given items must be in a list or set where each item is a
+ tuple of the form: (jid, node, name).
- def set_items(self, jid=None, node=None, **kwargs):
+ Arguments:
+ jid -- The JID to modify.
+ node -- Optional node to modify.
+ items -- A series of items in tuple format.
+ """
self._run_node_handler('set_items', jid, node, kwargs)
def del_items(self, jid=None, node=None, **kwargs):
+ """
+ Remove all items from the given JID/node combination.
+
+ Arguments:
+ jid -- The JID to modify.
+ node -- Optional node to modify.
+ """
self._run_node_handler('del_items', jid, node, kwargs)
+ def add_item(self, jid=None, node=None, **kwargs):
+ """
+ Add a new item element to the given JID/node combination.
+
+ Each item is required to have a JID, but may also specify
+ a node value to reference non-addressable entities.
+
+ Arguments:
+ jid -- The JID to modify.
+ node -- The node to modify.
+ ijid -- The JID for the item.
+ inode -- Optional node for the item.
+ name -- Optional name for the item.
+ """
+ self._run_node_handler('add_item', jid, node, kwargs)
+
+ def del_item(self, jid=None, node=None, **kwargs):
+ """
+ Remove a single item from the given JID/node combination.
+
+ Arguments:
+ jid -- The JID to modify.
+ node -- The node to modify.
+ ijid -- The item's JID.
+ inode -- The item's node.
+ """
+ self._run_node_handler('del_item', jid, node, kwargs)
+
def add_identity(self, jid=None, node=None, **kwargs):
+ """
+ Add a new identity to the given JID/node combination.
+
+ Each identity must be unique in terms of all four identity
+ components: category, type, name, and language.
+
+ Multiple, identical category/type pairs are allowed only
+ if the xml:lang values are different. Likewise, multiple
+ category/type/xml:lang pairs are allowed so long as the
+ names are different. A category and type is always required.
+
+ Arguments:
+ jid -- The JID to modify.
+ node -- The node to modify.
+ category -- The identity's category.
+ itype -- The identity's type.
+ name -- Optional name for the identity.
+ lang -- Optional two-letter language code.
+ """
self._run_node_handler('add_identity', jid, node, kwargs)
def add_feature(self, jid=None, node=None, **kwargs):
+ """
+ Add a feature to a JID/node combination.
+
+ Arguments:
+ jid -- The JID to modify.
+ node -- The node to modify.
+ feature -- The namespace of the supported feature.
+ """
self._run_node_handler('add_feature', jid, node, kwargs)
def del_identity(self, jid=None, node=None, **kwargs):
+ """
+ Remove an identity from the given JID/node combination.
+
+ Arguments:
+ jid -- The JID to modify.
+ node -- The node to modify.
+ category -- The identity's category.
+ itype -- The identity's type value.
+ 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)
def del_feature(self, jid=None, node=None, **kwargs):
+ """
+ Remove a feature from a given JID/node combination.
+
+ Arguments:
+ jid -- The JID to modify.
+ node -- The node to modify.
+ feature -- The feature's namespace.
+ """
self._run_node_handler('del_feature', jid, node, kwargs)
- def add_item(self, jid=None, node=None, **kwargs):
- self._run_node_handler('add_item', jid, node, kwargs)
+ def set_identities(self, jid=None, node=None, **kwargs):
+ """
+ Add or replace all identities for the given JID/node combination.
- def del_item(self, jid=None, node=None, **kwargs):
- self._run_node_handler('del_item', jid, node, kwargs)
+ The identities must be in a set where each identity is a tuple
+ of the form: (category, type, lang, name)
+
+ Arguments:
+ jid -- The JID to modify.
+ node -- The node to modify.
+ identities -- A set of identities in tuple form.
+ lang -- Optional, xml:lang value.
+ """
+ self._run_node_handler('set_identities', jid, node, kwargs)
- def _run_node_handler(self, htype, jid, node, data=None):
+ def del_identities(self, jid=None, node=None, **kwargs):
+ """
+ Remove all identities for a JID/node combination.
+
+ If a language is specified, only identities using that
+ language will be removed.
+
+ Arguments:
+ jid -- The JID to modify.
+ node -- The node to modify.
+ lang -- Optional. If given, only remove identities
+ using this xml:lang value.
+ """
+ self._run_node_handler('del_identities', jid, node, kwargs)
+
+ def set_features(self, jid=None, node=None, **kwargs):
+ """
+ Add or replace the set of supported features
+ for a JID/node combination.
+
+ Arguments:
+ jid -- The JID to modify.
+ node -- The node to modify.
+ features -- The new set of supported features.
+ """
+ self._run_node_handler('set_features', jid, node, kwargs)
+
+ def del_features(self, jid=None, node=None, **kwargs):
+ """
+ Remove all features from a JID/node combination.
+
+ Arguments:
+ jid -- The JID to modify.
+ node -- The node to modify.
+ """
+ self._run_node_handler('del_features', jid, node, kwargs)
+
+ def _run_node_handler(self, htype, jid, node, data={}):
"""
Execute the most specific node handler for the given
JID/node combination.
@@ -218,19 +461,19 @@ class xep_0030(base_plugin):
htype -- The handler type to execute.
jid -- The JID requested.
node -- The node requested.
- dat -- Optional, custom data to pass to the handler.
+ data -- Optional, custom data to pass to the handler.
"""
if jid is None:
jid = self.xmpp.boundjid.full
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)
+ 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
@@ -311,4 +554,3 @@ class xep_0030(base_plugin):
"Using default disco#info feature.")
info.add_feature(info.namespace)
return info
-
diff --git a/sleekxmpp/plugins/xep_0030/stanza/disco.py b/sleekxmpp/plugins/xep_0030/stanza/disco.py
deleted file mode 100644
index e69de29b..00000000
--- a/sleekxmpp/plugins/xep_0030/stanza/disco.py
+++ /dev/null
diff --git a/sleekxmpp/plugins/xep_0030/stanza/items.py b/sleekxmpp/plugins/xep_0030/stanza/items.py
index 319e666f..a1fb819c 100644
--- a/sleekxmpp/plugins/xep_0030/stanza/items.py
+++ b/sleekxmpp/plugins/xep_0030/stanza/items.py
@@ -12,8 +12,6 @@ from sleekxmpp.xmlstream import ElementBase, ET
class DiscoItems(ElementBase):
"""
-
-
Example disco#items stanzas:
<iq type="get">
<query xmlns="http://jabber.org/protocol/disco#items" />
@@ -74,7 +72,7 @@ class DiscoItems(ElementBase):
Arguments:
jid -- The JID for the item.
- node -- Optional additional information to reference
+ node -- Optional additional information to reference
non-addressable items.
name -- Optional human readable name for the item.
"""
diff --git a/sleekxmpp/plugins/xep_0030/static.py b/sleekxmpp/plugins/xep_0030/static.py
index f3693228..b0e931b4 100644
--- a/sleekxmpp/plugins/xep_0030/static.py
+++ b/sleekxmpp/plugins/xep_0030/static.py
@@ -35,6 +35,11 @@ class StaticDisco(object):
def __init__(self, xmpp):
"""
+ Create a static disco interface. Sets of disco#info and
+ disco#items are maintained for every given JID and node
+ combination. These stanzas are used to store disco
+ information in memory without any additional processing.
+
Arguments:
xmpp -- The main SleekXMPP object.
"""
@@ -52,7 +57,7 @@ class StaticDisco(object):
self.nodes[(jid, node)]['info']['node'] = node
self.nodes[(jid, node)]['items']['node'] = node
- def get_info(self, jid, node, data=None):
+ def get_info(self, jid, node, data):
if (jid, node) not in self.nodes:
if not node:
return DiscoInfo()
@@ -61,11 +66,11 @@ class StaticDisco(object):
else:
return self.nodes[(jid, node)]['info']
- def del_info(self, jid, node, data=None):
+ def del_info(self, jid, node, data):
if (jid, node) in self.nodes:
self.nodes[(jid, node)]['info'] = DiscoInfo()
- def get_items(self, jid, node, data=None):
+ def get_items(self, jid, node, data):
if (jid, node) not in self.nodes:
if not node:
return DiscoInfo()
@@ -74,14 +79,16 @@ class StaticDisco(object):
else:
return self.nodes[(jid, node)]['items']
- def set_items(self, jid, node, data=None):
- pass
+ def set_items(self, jid, node, data):
+ items = data.get('items', set())
+ self.add_node(jid, node)
+ self.nodes[(jid, node)]['items']['items'] = items
- def del_items(self, jid, node, data=None):
+ def del_items(self, jid, node, data):
if (jid, node) in self.nodes:
self.nodes[(jid, node)]['items'] = DiscoItems()
- def add_identity(self, jid, node, data={}):
+ def add_identity(self, jid, node, data):
self.add_node(jid, node)
self.nodes[(jid, node)]['info'].add_identity(
data.get('category', ''),
@@ -89,10 +96,12 @@ class StaticDisco(object):
data.get('name', None),
data.get('lang', None))
- def set_identities(self, jid, node, data=None):
- pass
+ def set_identities(self, jid, node, data):
+ identities = data.get('identities', set())
+ self.add_node(jid, node)
+ self.nodes[(jid, node)]['info']['identities'] = identities
- def del_identity(self, jid, node, data=None):
+ def del_identity(self, jid, node, data):
if (jid, node) not in self.nodes:
return
self.nodes[(jid, node)]['info'].del_identity(
@@ -101,27 +110,29 @@ class StaticDisco(object):
data.get('name', None),
data.get('lang', None))
-
- def add_feature(self, jid, node, data=None):
+ def add_feature(self, jid, node, data):
self.add_node(jid, node)
self.nodes[(jid, node)]['info'].add_feature(data.get('feature', ''))
- def set_features(self, jid, node, data=None):
- pass
+ def set_features(self, jid, node, data):
+ features = data.get('features', set())
+ self.add_node(jid, node)
+ self.nodes[(jid, node)]['info']['features'] = features
- def del_feature(self, jid, node, data=None):
+ def del_feature(self, jid, node, data):
if (jid, node) not in self.nodes:
return
self.nodes[(jid, node)]['info'].del_feature(data.get('feature', ''))
- def add_item(self, jid, node, data=None):
+ def add_item(self, jid, node, data):
self.add_node(jid, node)
self.nodes[(jid, node)]['items'].add_item(
data.get('ijid', ''),
node=data.get('inode', None),
name=data.get('name', None))
- def del_item(self, jid, node, data=None):
+ def del_item(self, jid, node, data):
if (jid, node) in self.nodes:
- self.nodes[(jid, node)]['items'].del_item(**data)
-
+ self.nodes[(jid, node)]['items'].del_item(
+ data.get('ijid', ''),
+ node=data.get('inode', None))