Merge branch 'develop' into stream_features

This commit is contained in:
Lance Stout 2011-03-22 20:48:28 -04:00
commit 306bdd8021
9 changed files with 336 additions and 72 deletions

View file

@ -52,6 +52,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0059', 'sleekxmpp/plugins/xep_0059',
'sleekxmpp/plugins/xep_0085', 'sleekxmpp/plugins/xep_0085',
'sleekxmpp/plugins/xep_0092', 'sleekxmpp/plugins/xep_0092',
'sleekxmpp/plugins/xep_0128',
'sleekxmpp/plugins/xep_0199', 'sleekxmpp/plugins/xep_0199',
] ]

View file

@ -60,10 +60,13 @@ class xep_0030(base_plugin):
disco_items_query -- Received a disco#items Iq query request. disco_items_query -- Received a disco#items Iq query request.
Attributes: Attributes:
stanza -- A reference to the module containing the stanza classes stanza -- A reference to the module containing the
provided by this plugin. stanza classes provided by this plugin.
static -- Object containing the default set of static node handlers. static -- Object containing the default set of
xmpp -- The main SleekXMPP object. static node handlers.
default_handlers -- A dictionary mapping operations to the default
global handler (by default, the static handlers).
xmpp -- The main SleekXMPP object.
Methods: Methods:
set_node_handler -- Assign a handler to a JID/node combination. set_node_handler -- Assign a handler to a JID/node combination.
@ -110,11 +113,10 @@ class xep_0030(base_plugin):
'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']
self.default_handlers = {}
self._handlers = {} self._handlers = {}
for op in self._disco_ops: for op in self._disco_ops:
self._handlers[op] = {'global': getattr(self.static, op), self._add_disco_op(op, getattr(self.static, op))
'jid': {},
'node': {}}
def post_init(self): def post_init(self):
"""Handle cross-plugin dependencies.""" """Handle cross-plugin dependencies."""
@ -123,6 +125,12 @@ class xep_0030(base_plugin):
register_stanza_plugin(DiscoItems, register_stanza_plugin(DiscoItems,
self.xmpp['xep_0059'].stanza.Set) self.xmpp['xep_0059'].stanza.Set)
def _add_disco_op(self, op, default_handler):
self.default_handlers[op] = default_handler
self._handlers[op] = {'global': default_handler,
'jid': {},
'node': {}}
def set_node_handler(self, htype, jid=None, node=None, handler=None): def set_node_handler(self, htype, jid=None, node=None, handler=None):
""" """
Add a node handler for the given hierarchy level and Add a node handler for the given hierarchy level and
@ -205,26 +213,29 @@ class xep_0030(base_plugin):
""" """
self.set_node_handler(htype, jid, node, None) self.set_node_handler(htype, jid, node, None)
def make_static(self, jid=None, node=None, handlers=None): def restore_defaults(self, jid=None, node=None, handlers=None):
""" """
Change all of a node's handlers to the default static Change all or some of a node's handlers to the default
handlers. Useful for manually overriding the contents handlers. Useful for manually overriding the contents
of a node that would otherwise be handled by a JID level of a node that would otherwise be handled by a JID level
or global level dynamic handler. or global level dynamic handler.
The default is to use the built-in static handlers, but that
may be changed by modifying self.default_handlers.
Arguments: Arguments:
jid -- The JID owning the node to modify. jid -- The JID owning the node to modify.
node -- The node to change to using static handlers. node -- The node to change to using static handlers.
handlers -- Optional list of handlers to change to the handlers -- Optional list of handlers to change to the
static version. If provided, only these default version. If provided, only these
handlers will be changed. Otherwise, all handlers will be changed. Otherwise, all
handlers will use the static version. handlers will use the default version.
""" """
if handlers is None: if handlers is None:
handlers = self._disco_ops handlers = self._disco_ops
for op in handlers: for op in handlers:
self.del_node_handler(op, jid, node) self.del_node_handler(op, jid, node)
self.set_node_handler(op, jid, node, getattr(self.static, op)) self.set_node_handler(op, jid, node, self.default_handlers[op])
def get_info(self, jid=None, node=None, local=False, **kwargs): def get_info(self, jid=None, node=None, local=False, **kwargs):
""" """
@ -609,3 +620,4 @@ class xep_0030(base_plugin):
# Retain some backwards compatibility # Retain some backwards compatibility
xep_0030.getInfo = xep_0030.get_info xep_0030.getInfo = xep_0030.get_info
xep_0030.getItems = xep_0030.get_items xep_0030.getItems = xep_0030.get_items
xep_0030.make_static = xep_0030.restore_defaults

View file

@ -120,7 +120,8 @@ class StaticDisco(object):
""" """
Replace the stored items data for a JID/node combination. Replace the stored items data for a JID/node combination.
The data parameter is not used. The data parameter may provided:
items -- A set of items in tuple format.
""" """
items = data.get('items', set()) items = data.get('items', set())
self.add_node(jid, node) self.add_node(jid, node)

View file

@ -1,51 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from . import base
from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.iq import Iq
from . xep_0030 import DiscoInfo, DiscoItems
from . xep_0004 import Form
class xep_0128(base.base_plugin):
"""
XEP-0128 Service Discovery Extensions
"""
def plugin_init(self):
self.xep = '0128'
self.description = 'Service Discovery Extensions'
registerStanzaPlugin(DiscoInfo, Form)
registerStanzaPlugin(DiscoItems, Form)
def extend_info(self, node, data=None):
if data is None:
data = {}
node = self.xmpp['xep_0030'].nodes.get(node, None)
if node is None:
self.xmpp['xep_0030'].add_node(node)
info = node.info
info['form']['type'] = 'result'
info['form'].setFields(data, default=None)
def extend_items(self, node, data=None):
if data is None:
data = {}
node = self.xmpp['xep_0030'].nodes.get(node, None)
if node is None:
self.xmpp['xep_0030'].add_node(node)
items = node.items
items['form']['type'] = 'result'
items['form'].setFields(data, default=None)

View file

@ -0,0 +1,10 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.xep_0128.static import StaticExtendedDisco
from sleekxmpp.plugins.xep_0128.extended_disco import xep_0128

View file

@ -0,0 +1,101 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
import sleekxmpp
from sleekxmpp import Iq
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.plugins.xep_0004 import Form
from sleekxmpp.plugins.xep_0030 import DiscoInfo
from sleekxmpp.plugins.xep_0128 import StaticExtendedDisco
class xep_0128(base_plugin):
"""
XEP-0128: Service Discovery Extensions
Allow the use of data forms to add additional identity
information to disco#info results.
Also see <http://www.xmpp.org/extensions/xep-0128.html>.
Attributes:
disco -- A reference to the XEP-0030 plugin.
static -- Object containing the default set of static
node handlers.
xmpp -- The main SleekXMPP object.
Methods:
set_extended_info -- Set extensions to a disco#info result.
add_extended_info -- Add an extension to a disco#info result.
del_extended_info -- Remove all extensions from a disco#info result.
"""
def plugin_init(self):
"""Start the XEP-0128 plugin."""
self.xep = '0128'
self.description = 'Service Discovery Extensions'
self._disco_ops = ['set_extended_info',
'add_extended_info',
'del_extended_info']
register_stanza_plugin(DiscoInfo, Form, iterable=True)
def post_init(self):
"""Handle cross-plugin dependencies."""
base_plugin.post_init(self)
self.disco = self.xmpp['xep_0030']
self.static = StaticExtendedDisco(self.disco.static)
self.disco.set_extended_info = self.set_extended_info
self.disco.add_extended_info = self.add_extended_info
self.disco.del_extended_info = self.del_extended_info
for op in self._disco_ops:
self.disco._add_disco_op(op, getattr(self.static, op))
def set_extended_info(self, jid=None, node=None, **kwargs):
"""
Set additional, extended identity information to a node.
Replaces any existing extended information.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
data -- Either a form, or a list of forms to use
as extended information, replacing any
existing extensions.
"""
self.disco._run_node_handler('set_extended_info', jid, node, kwargs)
def add_extended_info(self, jid=None, node=None, **kwargs):
"""
Add additional, extended identity information to a node.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
data -- Either a form, or a list of forms to add
as extended information.
"""
self.disco._run_node_handler('add_extended_info', jid, node, kwargs)
def del_extended_info(self, jid=None, node=None, **kwargs):
"""
Remove all extended identity information to a node.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
"""
self.disco._run_node_handler('del_extended_info', jid, node, kwargs)

View file

@ -0,0 +1,72 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
import sleekxmpp
from sleekxmpp.plugins.xep_0030 import StaticDisco
log = logging.getLogger(__name__)
class StaticExtendedDisco(object):
"""
Extend the default StaticDisco implementation to provide
support for extended identity information.
"""
def __init__(self, static):
"""
Augment the default XEP-0030 static handler object.
Arguments:
static -- The default static XEP-0030 handler object.
"""
self.static = static
def set_extended_info(self, jid, node, data):
"""
Replace the extended identity data for a JID/node combination.
The data parameter may provide:
data -- Either a single data form, or a list of data forms.
"""
self.del_extended_info(jid, node, data)
self.add_extended_info(jid, node, data)
def add_extended_info(self, jid, node, data):
"""
Add additional extended identity data for a JID/node combination.
The data parameter may provide:
data -- Either a single data form, or a list of data forms.
"""
self.static.add_node(jid, node)
forms = data.get('data', [])
if not isinstance(forms, list):
forms = [forms]
for form in forms:
self.static.nodes[(jid, node)]['info'].append(form)
def del_extended_info(self, jid, node, data):
"""
Replace the extended identity data for a JID/node combination.
The data parameter is not used.
"""
if (jid, node) not in self.static.nodes:
return
info = self.static.nodes[(jid, node)]['info']
for form in info['substanza']:
info.xml.remove(form.xml)

View file

@ -40,6 +40,7 @@ def register_stanza_plugin(stanza, plugin, iterable=False):
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
stanza.plugin_tag_map[tag] = plugin stanza.plugin_tag_map[tag] = plugin
if iterable: if iterable:
stanza.plugin_iterables = stanza.plugin_iterables.copy()
stanza.plugin_iterables.add(plugin) stanza.plugin_iterables.add(plugin)
@ -206,7 +207,7 @@ class ElementBase(object):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_iterables = set() plugin_iterables = set()
plugin_tag_map = {} plugin_tag_map = {}
subitem = None subitem = set()
is_extension = False is_extension = False
xml_ns = 'http://www.w3.org/XML/1998/namespace' xml_ns = 'http://www.w3.org/XML/1998/namespace'
@ -231,6 +232,10 @@ class ElementBase(object):
ElementBase.values = property(ElementBase._get_stanza_values, ElementBase.values = property(ElementBase._get_stanza_values,
ElementBase._set_stanza_values) ElementBase._set_stanza_values)
if self.subitem is not None:
for sub in self.subitem:
self.plugin_iterables.add(sub)
if self.setup(xml): if self.setup(xml):
# If we generated our own XML, then everything is ready. # If we generated our own XML, then everything is ready.
return return
@ -240,9 +245,6 @@ class ElementBase(object):
if child.tag in self.plugin_tag_map: if child.tag in self.plugin_tag_map:
plugin = self.plugin_tag_map[child.tag] plugin = self.plugin_tag_map[child.tag]
self.plugins[plugin.plugin_attrib] = plugin(child, self) self.plugins[plugin.plugin_attrib] = plugin(child, self)
if self.subitem is not None:
for sub in self.subitem:
self.plugin_iterables.add(sub)
for sub in self.plugin_iterables: for sub in self.plugin_iterables:
if child.tag == "{%s}%s" % (sub.namespace, sub.name): if child.tag == "{%s}%s" % (sub.namespace, sub.name):
self.iterables.append(sub(child, self)) self.iterables.append(sub(child, self))
@ -333,11 +335,20 @@ class ElementBase(object):
Plugin interfaces may accept a nested dictionary that Plugin interfaces may accept a nested dictionary that
will be used recursively. will be used recursively.
""" """
iterable_interfaces = [p.plugin_attrib for \
p in self.plugin_iterables]
for interface, value in values.items(): for interface, value in values.items():
if interface == 'substanzas': if interface == 'substanzas':
# Remove existing substanzas
for stanza in self.iterables:
self.xml.remove(stanza.xml)
self.iterables = []
# Add new substanzas
for subdict in value: for subdict in value:
if '__childtag__' in subdict: if '__childtag__' in subdict:
for subclass in self.subitem: for subclass in self.plugin_iterables:
child_tag = "{%s}%s" % (subclass.namespace, child_tag = "{%s}%s" % (subclass.namespace,
subclass.name) subclass.name)
if subdict['__childtag__'] == child_tag: if subdict['__childtag__'] == child_tag:
@ -348,9 +359,10 @@ class ElementBase(object):
elif interface in self.interfaces: elif interface in self.interfaces:
self[interface] = value self[interface] = value
elif interface in self.plugin_attrib_map: elif interface in self.plugin_attrib_map:
if interface not in self.plugins: if interface not in iterable_interfaces:
self.init_plugin(interface) if interface not in self.plugins:
self.plugins[interface].values = value self.init_plugin(interface)
self.plugins[interface].values = value
return self return self
def __getitem__(self, attrib): def __getitem__(self, attrib):

View file

@ -0,0 +1,106 @@
import sys
import time
import threading
from sleekxmpp.test import *
from sleekxmpp.xmlstream import ElementBase
class TestStreamExtendedDisco(SleekTest):
"""
Test using the XEP-0128 plugin.
"""
def tearDown(self):
sys.excepthook = sys.__excepthook__
self.stream_close()
def testUsingExtendedInfo(self):
self.stream_start(mode='client',
jid='tester@localhost',
plugins=['xep_0030',
'xep_0004',
'xep_0128'])
form = self.xmpp['xep_0004'].makeForm(ftype='result')
form.addField(var='FORM_TYPE', ftype='hidden', value='testing')
info_ns = 'http://jabber.org/protocol/disco#info'
self.xmpp['xep_0030'].add_identity(node='test',
category='client',
itype='bot')
self.xmpp['xep_0030'].add_feature(node='test', feature=info_ns)
self.xmpp['xep_0128'].set_extended_info(node='test', data=form)
self.recv("""
<iq type="get" id="test" to="tester@localhost">
<query xmlns="http://jabber.org/protocol/disco#info"
node="test" />
</iq>
""")
self.send("""
<iq type="result" id="test">
<query xmlns="http://jabber.org/protocol/disco#info"
node="test">
<identity category="client" type="bot" />
<feature var="http://jabber.org/protocol/disco#info" />
<x xmlns="jabber:x:data" type="result">
<field var="FORM_TYPE" type="hidden">
<value>testing</value>
</field>
</x>
</query>
</iq>
""")
def testUsingMultipleExtendedInfo(self):
self.stream_start(mode='client',
jid='tester@localhost',
plugins=['xep_0030',
'xep_0004',
'xep_0128'])
form1 = self.xmpp['xep_0004'].makeForm(ftype='result')
form1.addField(var='FORM_TYPE', ftype='hidden', value='testing')
form2 = self.xmpp['xep_0004'].makeForm(ftype='result')
form2.addField(var='FORM_TYPE', ftype='hidden', value='testing_2')
info_ns = 'http://jabber.org/protocol/disco#info'
self.xmpp['xep_0030'].add_identity(node='test',
category='client',
itype='bot')
self.xmpp['xep_0030'].add_feature(node='test', feature=info_ns)
self.xmpp['xep_0128'].set_extended_info(node='test', data=[form1, form2])
self.recv("""
<iq type="get" id="test" to="tester@localhost">
<query xmlns="http://jabber.org/protocol/disco#info"
node="test" />
</iq>
""")
self.send("""
<iq type="result" id="test">
<query xmlns="http://jabber.org/protocol/disco#info"
node="test">
<identity category="client" type="bot" />
<feature var="http://jabber.org/protocol/disco#info" />
<x xmlns="jabber:x:data" type="result">
<field var="FORM_TYPE" type="hidden">
<value>testing</value>
</field>
</x>
<x xmlns="jabber:x:data" type="result">
<field var="FORM_TYPE" type="hidden">
<value>testing_2</value>
</field>
</x>
</query>
</iq>
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamExtendedDisco)