summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sleekxmpp/jid.py23
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/field.py4
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/form.py42
-rw-r--r--sleekxmpp/plugins/xep_0332/http.py6
-rw-r--r--sleekxmpp/thirdparty/__init__.py1
-rw-r--r--sleekxmpp/thirdparty/orderedset.py89
-rw-r--r--sleekxmpp/xmlstream/stanzabase.py2
-rw-r--r--tests/test_stanza_xep_0004.py61
-rw-r--r--tests/test_stream_xep_0050.py8
9 files changed, 198 insertions, 38 deletions
diff --git a/sleekxmpp/jid.py b/sleekxmpp/jid.py
index 613b272e..754a3d3a 100644
--- a/sleekxmpp/jid.py
+++ b/sleekxmpp/jid.py
@@ -72,19 +72,18 @@ JID_CACHE_LOCK = threading.Lock()
JID_CACHE_MAX_SIZE = 1024
def _cache(key, parts, locked):
- JID_CACHE[key] = (parts, locked)
- if len(JID_CACHE) > JID_CACHE_MAX_SIZE:
- with JID_CACHE_LOCK:
- while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
- found = None
- for key, item in JID_CACHE.items():
- if not item[1]: # if not locked
- found = key
- break
- if not found: # more than MAX_SIZE locked
- # warn?
+ with JID_CACHE_LOCK:
+ JID_CACHE[key] = (parts, locked)
+ while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
+ found = None
+ for key, item in JID_CACHE.items():
+ if not item[1]: # if not locked
+ found = key
break
- del JID_CACHE[found]
+ if not found: # more than MAX_SIZE locked
+ # warn?
+ break
+ del JID_CACHE[found]
# pylint: disable=c0103
#: The nodeprep profile of stringprep used to validate the local,
diff --git a/sleekxmpp/plugins/xep_0004/stanza/field.py b/sleekxmpp/plugins/xep_0004/stanza/field.py
index 51f85995..73e48758 100644
--- a/sleekxmpp/plugins/xep_0004/stanza/field.py
+++ b/sleekxmpp/plugins/xep_0004/stanza/field.py
@@ -13,8 +13,9 @@ class FormField(ElementBase):
namespace = 'jabber:x:data'
name = 'field'
plugin_attrib = 'field'
+ plugin_multi_attrib = 'fields'
interfaces = set(('answer', 'desc', 'required', 'value',
- 'options', 'label', 'type', 'var'))
+ 'label', 'type', 'var'))
sub_interfaces = set(('desc',))
plugin_tag_map = {}
plugin_attrib_map = {}
@@ -165,6 +166,7 @@ class FieldOption(ElementBase):
plugin_attrib = 'option'
interfaces = set(('label', 'value'))
sub_interfaces = set(('value',))
+ plugin_multi_attrib = 'options'
FormField.addOption = FormField.add_option
diff --git a/sleekxmpp/plugins/xep_0004/stanza/form.py b/sleekxmpp/plugins/xep_0004/stanza/form.py
index 1d733760..3dcc7821 100644
--- a/sleekxmpp/plugins/xep_0004/stanza/form.py
+++ b/sleekxmpp/plugins/xep_0004/stanza/form.py
@@ -9,7 +9,7 @@
import copy
import logging
-from sleekxmpp.thirdparty import OrderedDict
+from sleekxmpp.thirdparty import OrderedDict, OrderedSet
from sleekxmpp.xmlstream import ElementBase, ET
from sleekxmpp.plugins.xep_0004.stanza import FormField
@@ -22,8 +22,7 @@ class Form(ElementBase):
namespace = 'jabber:x:data'
name = 'x'
plugin_attrib = 'form'
- interfaces = set(('fields', 'instructions', 'items',
- 'reported', 'title', 'type', 'values'))
+ interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
sub_interfaces = set(('title',))
form_types = set(('cancel', 'form', 'result', 'submit'))
@@ -43,12 +42,12 @@ class Form(ElementBase):
@property
def field(self):
- return self['fields']
+ return self.get_fields()
def set_type(self, ftype):
self._set_attr('type', ftype)
if ftype == 'submit':
- fields = self['fields']
+ fields = self.get_fields()
for var in fields:
field = fields[var]
del field['type']
@@ -74,7 +73,8 @@ class Form(ElementBase):
field['desc'] = desc
field['required'] = required
if options is not None:
- field['options'] = options
+ for option in options:
+ field.add_option(**option)
else:
del field['type']
self.append(field)
@@ -169,7 +169,7 @@ class Form(ElementBase):
def get_reported(self):
fields = OrderedDict()
xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
- FormField.namespace))
+ FormField.namespace))
for field in xml:
field = FormField(xml=field)
fields[field['var']] = field
@@ -177,7 +177,7 @@ class Form(ElementBase):
def get_values(self):
values = OrderedDict()
- fields = self['fields']
+ fields = self.get_fields()
for var in fields:
values[var] = fields[var]['value']
return values
@@ -219,17 +219,33 @@ class Form(ElementBase):
self.add_item(item)
def set_reported(self, reported):
+ """
+ This either needs a dictionary or dictionaries or a dictionary of form fields.
+ :param reported:
+ :return:
+ """
for var in reported:
field = reported[var]
- field['var'] = var
- self.add_reported(var, **field)
+
+ if isinstance(field, dict):
+ self.add_reported(**field)
+ else:
+ reported = self.xml.find('{%s}reported' % self.namespace)
+ if reported is None:
+ reported = ET.Element('{%s}reported' % self.namespace)
+ self.xml.append(reported)
+
+ fieldXML = ET.Element('{%s}field' % FormField.namespace)
+ reported.append(fieldXML)
+ new_field = FormField(xml=fieldXML)
+ new_field.values = field.values
def set_values(self, values):
- fields = self['fields']
+ fields = self.get_fields()
for field in values:
- if field not in fields:
+ if field not in self.get_fields():
fields[field] = self.add_field(var=field)
- fields[field]['value'] = values[field]
+ self.get_fields()[field]['value'] = values[field]
def merge(self, other):
new = copy.copy(self)
diff --git a/sleekxmpp/plugins/xep_0332/http.py b/sleekxmpp/plugins/xep_0332/http.py
index 03d88b65..70bcafa6 100644
--- a/sleekxmpp/plugins/xep_0332/http.py
+++ b/sleekxmpp/plugins/xep_0332/http.py
@@ -108,6 +108,8 @@ class XEP_0332(BasePlugin):
iq['http-req']['method'] = method
iq['http-req']['resource'] = resource
iq['http-req']['version'] = '1.1' # TODO: set this implicitly
+ if 'id' in kwargs:
+ iq['id'] = kwargs["id"]
if data is not None:
iq['http-req']['data'] = data
return iq.send(
@@ -127,6 +129,8 @@ class XEP_0332(BasePlugin):
iq['http-resp']['code'] = code
iq['http-resp']['message'] = message
iq['http-resp']['version'] = '1.1' # TODO: set this implicitly
+ if 'id' in kwargs:
+ iq['id'] = kwargs["id"]
if data is not None:
iq['http-resp']['data'] = data
return iq.send(
@@ -145,6 +149,8 @@ class XEP_0332(BasePlugin):
iq['error']['code'] = ecode
iq['error']['type'] = etype
iq['error']['condition'] = econd
+ if 'id' in kwargs:
+ iq['id'] = kwargs["id"]
return iq.send(
timeout=kwargs.get('timeout', None),
block=kwargs.get('block', True),
diff --git a/sleekxmpp/thirdparty/__init__.py b/sleekxmpp/thirdparty/__init__.py
index 2a1db717..337598ac 100644
--- a/sleekxmpp/thirdparty/__init__.py
+++ b/sleekxmpp/thirdparty/__init__.py
@@ -10,3 +10,4 @@ except:
from sleekxmpp.thirdparty import socks
from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso
+from sleekxmpp.thirdparty.orderedset import OrderedSet
diff --git a/sleekxmpp/thirdparty/orderedset.py b/sleekxmpp/thirdparty/orderedset.py
new file mode 100644
index 00000000..f6642db3
--- /dev/null
+++ b/sleekxmpp/thirdparty/orderedset.py
@@ -0,0 +1,89 @@
+# Copyright (c) 2009 Raymond Hettinger
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+
+import collections
+
+class OrderedSet(collections.MutableSet):
+
+ def __init__(self, iterable=None):
+ self.end = end = []
+ end += [None, end, end] # sentinel node for doubly linked list
+ self.map = {} # key --> [key, prev, next]
+ if iterable is not None:
+ self |= iterable
+
+ def __len__(self):
+ return len(self.map)
+
+ def __contains__(self, key):
+ return key in self.map
+
+ def add(self, key):
+ if key not in self.map:
+ end = self.end
+ curr = end[1]
+ curr[2] = end[1] = self.map[key] = [key, curr, end]
+
+ def discard(self, key):
+ if key in self.map:
+ key, prev, next = self.map.pop(key)
+ prev[2] = next
+ next[1] = prev
+
+ def __iter__(self):
+ end = self.end
+ curr = end[2]
+ while curr is not end:
+ yield curr[0]
+ curr = curr[2]
+
+ def __reversed__(self):
+ end = self.end
+ curr = end[1]
+ while curr is not end:
+ yield curr[0]
+ curr = curr[1]
+
+ def pop(self, last=True):
+ if not self:
+ raise KeyError('set is empty')
+ key = self.end[1][0] if last else self.end[2][0]
+ self.discard(key)
+ return key
+
+ def __repr__(self):
+ if not self:
+ return '%s()' % (self.__class__.__name__,)
+ return '%s(%r)' % (self.__class__.__name__, list(self))
+
+ def __eq__(self, other):
+ if isinstance(other, OrderedSet):
+ return len(self) == len(other) and list(self) == list(other)
+ return set(self) == set(other)
+
+
+if __name__ == '__main__':
+ s = OrderedSet('abracadaba')
+ t = OrderedSet('simsalabim')
+ print(s | t)
+ print(s & t)
+ print(s - t) \ No newline at end of file
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
index 11c8dd67..c2e0f718 100644
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ b/sleekxmpp/xmlstream/stanzabase.py
@@ -563,7 +563,7 @@ class ElementBase(object):
.. versionadded:: 1.0-Beta1
"""
- values = {}
+ values = OrderedDict()
values['lang'] = self['lang']
for interface in self.interfaces:
if isinstance(self[interface], JID):
diff --git a/tests/test_stanza_xep_0004.py b/tests/test_stanza_xep_0004.py
index 9056c663..b87afb24 100644
--- a/tests/test_stanza_xep_0004.py
+++ b/tests/test_stanza_xep_0004.py
@@ -11,8 +11,8 @@ class TestDataForms(SleekTest):
def setUp(self):
register_stanza_plugin(Message, xep_0004.Form)
- register_stanza_plugin(xep_0004.Form, xep_0004.FormField)
- register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption)
+ register_stanza_plugin(xep_0004.Form, xep_0004.FormField, iterable=True)
+ register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption, iterable=True)
def testMultipleInstructions(self):
"""Testing using multiple instructions elements in a data form."""
@@ -68,7 +68,7 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]}
- form['fields'] = fields
+ form.set_fields(fields)
self.check(msg, """
@@ -141,13 +141,13 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]}
- form['fields'] = fields
+ form.set_fields(fields)
form['type'] = 'submit'
- form['values'] = {'f1': 'username',
+ form.set_values({'f1': 'username',
'f2': 'hunter2',
'f3': 'A long\nmultiline\nmessage',
- 'f4': 'cool'}
+ 'f4': 'cool'})
self.check(form, """
<x xmlns="jabber:x:data" type="submit">
@@ -189,7 +189,7 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]}
- form['fields'] = fields
+ form.set_fields(fields)
form['type'] = 'cancel'
@@ -197,5 +197,52 @@ class TestDataForms(SleekTest):
<x xmlns="jabber:x:data" type="cancel" />
""")
+ def testReported(self):
+ msg = self.Message()
+ form = msg['form']
+ form['type'] = 'result'
+
+ form.add_reported(var='f1', ftype='text-single', label='Username')
+
+ form.add_item({'f1': 'username@example.org'})
+
+ self.check(msg, """
+ <message>
+ <x xmlns="jabber:x:data" type="result">
+ <reported>
+ <field var="f1" type="text-single" label="Username" />
+ </reported>
+ <item>
+ <field var="f1">
+ <value>username@example.org</value>
+ </field>
+ </item>
+ </x>
+ </message>
+ """)
+
+ def testSetReported(self):
+ msg = self.Message()
+ form = msg['form']
+ form['type'] = 'result'
+
+ reported = {'f1': {
+ 'var': 'f1',
+ 'type': 'text-single',
+ 'label': 'Username'
+ }}
+
+ form.set_reported(reported)
+
+ self.check(msg, """
+ <message>
+ <x xmlns="jabber:x:data" type="result">
+ <reported>
+ <field var="f1" type="text-single" label="Username" />
+ </reported>
+ </x>
+ </message>
+ """)
+
suite = unittest.TestLoader().loadTestsFromTestCase(TestDataForms)
diff --git a/tests/test_stream_xep_0050.py b/tests/test_stream_xep_0050.py
index 261a0057..0a3eb718 100644
--- a/tests/test_stream_xep_0050.py
+++ b/tests/test_stream_xep_0050.py
@@ -119,7 +119,7 @@ class TestAdHocCommands(SleekTest):
def handle_command(iq, session):
def handle_form(form, session):
- results.append(form['values']['foo'])
+ results.append(form.get_values()['foo'])
form = self.xmpp['xep_0004'].makeForm('form')
form.addField(var='foo', ftype='text-single', label='Foo')
@@ -191,10 +191,10 @@ class TestAdHocCommands(SleekTest):
def handle_command(iq, session):
def handle_step2(form, session):
- results.append(form['values']['bar'])
+ results.append(form.get_values()['bar'])
def handle_step1(form, session):
- results.append(form['values']['foo'])
+ results.append(form.get_values()['foo'])
form = self.xmpp['xep_0004'].makeForm('form')
form.addField(var='bar', ftype='text-single', label='Bar')
@@ -426,7 +426,7 @@ class TestAdHocCommands(SleekTest):
def handle_form(forms, session):
for form in forms:
- results.append(form['values']['FORM_TYPE'])
+ results.append(form.get_values()['FORM_TYPE'])
form1 = self.xmpp['xep_0004'].makeForm('form')
form1.addField(var='FORM_TYPE', ftype='hidden', value='form_1')