From 100e504b7fdcb889f20abbb2b4843fb01e0d267c Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Fri, 22 Jun 2012 18:19:17 -0700
Subject: Resolve xml:lang issue with duplicated elements depending on
 ordering.

---
 sleekxmpp/xmlstream/stanzabase.py |  87 ++++++-----
 tests/test_stanza_element.py      | 321 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 365 insertions(+), 43 deletions(-)

diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
index 88276ddf..4af441cc 100644
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ b/sleekxmpp/xmlstream/stanzabase.py
@@ -421,12 +421,6 @@ class ElementBase(object):
         #: ``'{namespace}elementname'``.
         self.tag = self.tag_name()
 
-        if 'lang' not in self.interfaces:
-            if isinstance(self.interfaces, tuple):
-                self.interfaces += ('lang',)
-            else:
-                self.interfaces.add('lang')
-
         #: A :class:`weakref.weakref` to the parent stanza, if there is one.
         #: If not, then :attr:`parent` is ``None``.
         self.parent = None
@@ -574,6 +568,7 @@ class ElementBase(object):
         .. versionadded:: 1.0-Beta1
         """
         values = {}
+        values['lang'] = self['lang']
         for interface in self.interfaces:
             values[interface] = self[interface]
             if interface in self.lang_interfaces:
@@ -629,6 +624,8 @@ class ElementBase(object):
                                 sub.values = subdict
                                 self.iterables.append(sub)
                                 break
+            elif interface == 'lang':
+                self[interface] = value
             elif interface in self.interfaces:
                 self[full_interface] = value
             elif interface in self.plugin_attrib_map:
@@ -678,7 +675,7 @@ class ElementBase(object):
 
         if attrib == 'substanzas':
             return self.iterables
-        elif attrib in self.interfaces:
+        elif attrib in self.interfaces or attrib == 'lang':
             get_method = "get_%s" % attrib.lower()
             get_method2 = "get%s" % attrib.title()
 
@@ -752,7 +749,7 @@ class ElementBase(object):
         if lang and attrib in self.lang_interfaces:
             kwargs['lang'] = lang
 
-        if attrib in self.interfaces:
+        if attrib in self.interfaces or attrib == 'lang':
             if value is not None:
                 set_method = "set_%s" % attrib.lower()
                 set_method2 = "set%s" % attrib.title()
@@ -838,7 +835,7 @@ class ElementBase(object):
         if lang and attrib in self.lang_interfaces:
             kwargs['lang'] = lang
 
-        if attrib in self.interfaces:
+        if attrib in self.interfaces or attrib == 'lang':
             del_method = "del_%s" % attrib.lower()
             del_method2 = "del%s" % attrib.title()
 
@@ -973,10 +970,6 @@ class ElementBase(object):
         :param keep: Indicates if the element should be kept if its text is
                      removed. Defaults to False.
         """
-        path = self._fix_ns(name, split=True)
-        element = self.xml.find(name)
-        parent = self.xml
-
         default_lang = self.get_lang()
         if lang is None:
             lang = default_lang
@@ -984,32 +977,51 @@ class ElementBase(object):
         if not text and not keep:
             return self._del_sub(name, lang=lang)
 
-        if element is None:
-            # We need to add the element. If the provided name was
-            # an XPath expression, some of the intermediate elements
-            # may already exist. If so, we want to use those instead
-            # of generating new elements.
-            last_xml = self.xml
-            walked = []
-            for ename in path:
-                walked.append(ename)
-                element = self.xml.find("/".join(walked))
-                if element is None:
-                    element = ET.Element(ename)
-                    if lang:
-                        element.attrib['{%s}lang' % XML_NS] = lang
-                    last_xml.append(element)
-                parent = last_xml
-                last_xml = element
-            element = last_xml
+        path = self._fix_ns(name, split=True)
+        name = path[-1]
+        parent = self.xml
 
-        if lang:
-            if element.attrib.get('{%s}lang' % XML_NS, default_lang) != lang:
-                element = ET.Element(ename)
-                element.attrib['{%s}lang' % XML_NS] = lang
-                parent.append(element)
+        # The first goal is to find the parent of the subelement, or, if
+        # we can't find that, the closest grandparent element.
+        missing_path = []
+        search_order = path[:-1]
+        while search_order:
+            parent = self.xml.find('/'.join(search_order))
+            ename = search_order.pop()
+            if parent is not None:
+                break
+            else:
+                missing_path.append(ename)
+        missing_path.reverse()
+
+        # Find all existing elements that match the desired
+        # element path (there may be multiples due to different
+        # languages values).
+        if parent is not None:
+            elements = self.xml.findall('/'.join(path))
+        else:
+            parent = self.xml
+            elements = []
+
+        # Insert the remaining grandparent elements that don't exist yet.
+        for ename in missing_path:
+            element = ET.Element(ename)
+            parent.append(element)
+            parent = element
+
+        # Re-use an existing element with the proper language, if one exists.
+        for element in elements:
+            elang = element.attrib.get('{%s}lang' % XML_NS, default_lang)
+            if not lang and elang == default_lang or lang and lang == elang:
+                element.text = text
+                return element
 
+        # No useable element exists, so create a new one.
+        element = ET.Element(name)
         element.text = text
+        if lang and lang != default_lang:
+            element.attrib['{%s}lang' % XML_NS] = lang
+        parent.append(element)
         return element
 
     def _set_all_sub_text(self, name, values, keep=False, lang=None):
@@ -1184,6 +1196,7 @@ class ElementBase(object):
         out = []
         out += [x for x in self.interfaces]
         out += [x for x in self.loaded_plugins]
+        out.append('lang')
         if self.iterables:
             out.append('substanzas')
         return out
@@ -1263,7 +1276,7 @@ class ElementBase(object):
         """
         return "{%s}%s" % (cls.namespace, cls.name)
 
-    def get_lang(self):
+    def get_lang(self, lang=None):
         result = self.xml.attrib.get('{%s}lang' % XML_NS, '')
         if not result and self.parent and self.parent():
             return self.parent()['lang']
diff --git a/tests/test_stanza_element.py b/tests/test_stanza_element.py
index 1b47e733..b7ccdb87 100644
--- a/tests/test_stanza_element.py
+++ b/tests/test_stanza_element.py
@@ -1,5 +1,6 @@
 from sleekxmpp.test import *
 from sleekxmpp.xmlstream.stanzabase import ElementBase
+from sleekxmpp.thirdparty import OrderedDict
 
 
 class TestElementBase(SleekTest):
@@ -760,7 +761,7 @@ class TestElementBase(SleekTest):
           <foo xmlns="foo" />
         """)
 
-        self.assertFalse(stanza['bar'], 
+        self.assertFalse(stanza['bar'],
                 "Returned True for missing bool interface element.")
 
         stanza['bar'] = True
@@ -797,7 +798,7 @@ class TestElementBase(SleekTest):
             namespace = 'baz'
             plugin_attrib = name
             plugin_multi_attrib = 'bazs'
-           
+
         register_stanza_plugin(TestStanza, TestMultiStanza1, iterable=True)
         register_stanza_plugin(TestStanza, TestMultiStanza2, iterable=True)
 
@@ -829,9 +830,9 @@ class TestElementBase(SleekTest):
               <baz xmlns="baz" />
             """)
 
-        self.assertEqual(len(bars), 2, 
+        self.assertEqual(len(bars), 2,
                 "Wrong number of <bar /> stanzas: %s" % len(bars))
-        self.assertEqual(len(bazs), 2, 
+        self.assertEqual(len(bazs), 2,
                 "Wrong number of <baz /> stanzas: %s" % len(bazs))
 
     def testSetMultiAttrib(self):
@@ -853,7 +854,7 @@ class TestElementBase(SleekTest):
             namespace = 'baz'
             plugin_attrib = name
             plugin_multi_attrib = 'bazs'
-           
+
         register_stanza_plugin(TestStanza, TestMultiStanza1, iterable=True)
         register_stanza_plugin(TestStanza, TestMultiStanza2, iterable=True)
 
@@ -906,7 +907,7 @@ class TestElementBase(SleekTest):
             namespace = 'baz'
             plugin_attrib = name
             plugin_multi_attrib = 'bazs'
-           
+
         register_stanza_plugin(TestStanza, TestMultiStanza1, iterable=True)
         register_stanza_plugin(TestStanza, TestMultiStanza2, iterable=True)
 
@@ -938,5 +939,313 @@ class TestElementBase(SleekTest):
         self.assertEqual(len(stanza['substanzas']), 2,
             "Wrong number of substanzas: %s" % len(stanza['substanzas']))
 
+    def testDefaultLang(self):
+        """Test setting a normal subinterface when a default language is set"""
+
+        class TestStanza(ElementBase):
+            name = 'foo'
+            namespace = 'test'
+            interfaces = set(['test'])
+            sub_interfaces = interfaces
+            lang_interfaces = interfaces
+
+        stanza = TestStanza()
+        stanza['lang'] = 'sv'
+        stanza['test'] = 'hej'
+
+        self.check(stanza, """
+          <foo xmlns="test" xml:lang="sv">
+            <test>hej</test>
+          </foo>
+        """)
+
+        self.assertEqual(stanza['test'], 'hej',
+                "Incorrect subinterface value: %s" % stanza['test'])
+
+        self.assertEqual(stanza['test|sv'], 'hej',
+                "Incorrect subinterface value: %s" % stanza['test|sv'])
+
+    def testSpecifyLangWithDefault(self):
+        """Test specifying various languages."""
+
+        class TestStanza(ElementBase):
+            name = 'foo'
+            namespace = 'test'
+            interfaces = set(['test'])
+            sub_interfaces = interfaces
+            lang_interfaces = interfaces
+
+        stanza = TestStanza()
+        stanza['lang'] = 'sv'
+        stanza['test'] = 'hej'
+        stanza['test|en'] = 'hi'
+        stanza['test|es'] = 'hola'
+
+        self.check(stanza, """
+          <foo xmlns="test" xml:lang="sv">
+            <test>hej</test>
+            <test xml:lang="en">hi</test>
+            <test xml:lang="es">hola</test>
+          </foo>
+        """)
+
+        self.assertEqual(stanza['test'], 'hej',
+                "Incorrect subinterface value: %s" % stanza['test'])
+
+        self.assertEqual(stanza['test|sv'], 'hej',
+                "Incorrect subinterface value: %s" % stanza['test|sv'])
+
+        self.assertEqual(stanza['test|en'], 'hi',
+                "Incorrect subinterface value: %s" % stanza['test|en'])
+
+        self.assertEqual(stanza['test|es'], 'hola',
+                "Incorrect subinterface value: %s" % stanza['test|es'])
+
+    def testSpecifyLangWithNoDefault(self):
+        """Test specifying various languages."""
+
+        class TestStanza(ElementBase):
+            name = 'foo'
+            namespace = 'test'
+            interfaces = set(['test'])
+            sub_interfaces = interfaces
+            lang_interfaces = interfaces
+
+        stanza = TestStanza()
+        stanza['test'] = 'hej'
+        stanza['test|en'] = 'hi'
+        stanza['test|es'] = 'hola'
+
+        self.check(stanza, """
+          <foo xmlns="test">
+            <test>hej</test>
+            <test xml:lang="en">hi</test>
+            <test xml:lang="es">hola</test>
+          </foo>
+        """)
+
+        self.assertEqual(stanza['test'], 'hej',
+                "Incorrect subinterface value: %s" % stanza['test'])
+
+        self.assertEqual(stanza['test|en'], 'hi',
+                "Incorrect subinterface value: %s" % stanza['test|en'])
+
+        self.assertEqual(stanza['test|es'], 'hola',
+                "Incorrect subinterface value: %s" % stanza['test|es'])
+
+    def testModifyLangInterfaceWithDefault(self):
+        """Test resetting an interface when a default lang is used."""
+
+        class TestStanza(ElementBase):
+            name = 'foo'
+            namespace = 'test'
+            interfaces = set(['test'])
+            sub_interfaces = interfaces
+            lang_interfaces = interfaces
+
+        stanza = TestStanza()
+        stanza['lang'] = 'es'
+        stanza['test'] = 'hola'
+        stanza['test|en'] = 'hi'
+
+        self.check(stanza, """
+          <foo xmlns="test" xml:lang="es">
+            <test>hola</test>
+            <test xml:lang="en">hi</test>
+          </foo>
+        """)
+
+        stanza['test'] = 'adios'
+        stanza['test|en'] = 'bye'
+
+        self.check(stanza, """
+          <foo xmlns="test" xml:lang="es">
+            <test>adios</test>
+            <test xml:lang="en">bye</test>
+          </foo>
+        """)
+
+        self.assertEqual(stanza['test'], 'adios',
+                "Incorrect subinterface value: %s" % stanza['test'])
+
+        self.assertEqual(stanza['test|es'], 'adios',
+                "Incorrect subinterface value: %s" % stanza['test|es'])
+
+        self.assertEqual(stanza['test|en'], 'bye',
+                "Incorrect subinterface value: %s" % stanza['test|en'])
+
+        stanza['test|es'] = 'hola'
+
+        self.check(stanza, """
+          <foo xmlns="test" xml:lang="es">
+            <test>hola</test>
+            <test xml:lang="en">bye</test>
+          </foo>
+        """)
+
+        self.assertEqual(stanza['test'], 'hola',
+                "Incorrect subinterface value: %s" % stanza['test'])
+
+        self.assertEqual(stanza['test|es'], 'hola',
+                "Incorrect subinterface value: %s" % stanza['test|es'])
+
+    def testModifyLangInterfaceWithNoDefault(self):
+        """Test resetting an interface when no default lang is used."""
+
+        class TestStanza(ElementBase):
+            name = 'foo'
+            namespace = 'test'
+            interfaces = set(['test'])
+            sub_interfaces = interfaces
+            lang_interfaces = interfaces
+
+        stanza = TestStanza()
+        stanza['test'] = 'hola'
+        stanza['test|en'] = 'hi'
+
+        self.check(stanza, """
+          <foo xmlns="test">
+            <test>hola</test>
+            <test xml:lang="en">hi</test>
+          </foo>
+        """)
+
+        stanza['test'] = 'adios'
+        stanza['test|en'] = 'bye'
+
+        self.check(stanza, """
+          <foo xmlns="test">
+            <test>adios</test>
+            <test xml:lang="en">bye</test>
+          </foo>
+        """)
+
+        self.assertEqual(stanza['test'], 'adios',
+                "Incorrect subinterface value: %s" % stanza['test'])
+
+        self.assertEqual(stanza['test'], 'adios',
+                "Incorrect subinterface value: %s" % stanza['test|es'])
+
+        self.assertEqual(stanza['test|en'], 'bye',
+                "Incorrect subinterface value: %s" % stanza['test|en'])
+
+    def testDelInterfacesWithDefaultLang(self):
+        """Test deleting interfaces with a default lang set."""
+
+        class TestStanza(ElementBase):
+            name = 'foo'
+            namespace = 'test'
+            interfaces = set(['test'])
+            sub_interfaces = interfaces
+            lang_interfaces = interfaces
+
+        stanza = TestStanza()
+        stanza['lang'] = 'en'
+        stanza['test'] = 'hi'
+        stanza['test|no'] = 'hej'
+        stanza['test|fr'] = 'bonjour'
+
+        self.check(stanza, """
+          <foo xmlns="test" xml:lang="en">
+            <test>hi</test>
+            <test xml:lang="no">hej</test>
+            <test xml:lang="fr">bonjour</test>
+          </foo>
+        """)
+
+        del stanza['test']
+
+        self.check(stanza, """
+          <foo xmlns="test" xml:lang="en">
+            <test xml:lang="no">hej</test>
+            <test xml:lang="fr">bonjour</test>
+          </foo>
+        """)
+
+        del stanza['test|no']
+
+        self.check(stanza, """
+          <foo xmlns="test" xml:lang="en">
+            <test xml:lang="fr">bonjour</test>
+          </foo>
+        """)
+
+    def testDelInterfacesWithNoDefaultLang(self):
+        """Test deleting interfaces with no default lang set."""
+
+        class TestStanza(ElementBase):
+            name = 'foo'
+            namespace = 'test'
+            interfaces = set(['test'])
+            sub_interfaces = interfaces
+            lang_interfaces = interfaces
+
+        stanza = TestStanza()
+        stanza['test'] = 'hi'
+        stanza['test|no'] = 'hej'
+        stanza['test|fr'] = 'bonjour'
+
+        self.check(stanza, """
+          <foo xmlns="test">
+            <test>hi</test>
+            <test xml:lang="no">hej</test>
+            <test xml:lang="fr">bonjour</test>
+          </foo>
+        """)
+
+        del stanza['test']
+
+        self.check(stanza, """
+          <foo xmlns="test">
+            <test xml:lang="no">hej</test>
+            <test xml:lang="fr">bonjour</test>
+          </foo>
+        """)
+
+        del stanza['test|no']
+
+        self.check(stanza, """
+          <foo xmlns="test">
+            <test xml:lang="fr">bonjour</test>
+          </foo>
+        """)
+
+    def testStarLang(self):
+        """Test using interface|*."""
+
+        class TestStanza(ElementBase):
+            name = 'foo'
+            namespace = 'test'
+            interfaces = set(['test'])
+            sub_interfaces = interfaces
+            lang_interfaces = interfaces
+
+        data = OrderedDict()
+        data['en'] = 'hi'
+        data['fr'] = 'bonjour'
+        data['no'] = 'hej'
+
+        stanza = TestStanza()
+        stanza['test|*'] = data
+
+        self.check(stanza, """
+          <foo xmlns="test">
+            <test xml:lang="en">hi</test>
+            <test xml:lang="fr">bonjour</test>
+            <test xml:lang="no">hej</test>
+          </foo>
+        """)
+
+        data2 = stanza['test|*']
+
+        self.assertEqual(data, data2,
+                "Did not extract expected language data: %s" % data2)
+
+        del stanza['test|*']
+
+        self.check(stanza, """
+          <foo xmlns="test" />
+        """)
+
 
 suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase)
-- 
cgit v1.2.3


From dc501d19023f2d2409b7383e8c860a90ff0ecaf6 Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Fri, 22 Jun 2012 19:08:51 -0700
Subject: Mark message body and subject as language aware interfaces.

---
 sleekxmpp/stanza/message.py | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py
index 407802bd..992d7b7b 100644
--- a/sleekxmpp/stanza/message.py
+++ b/sleekxmpp/stanza/message.py
@@ -54,13 +54,14 @@ class Message(RootStanza):
         del_mucnick -- Dummy method to prevent deletion.
     """
 
-    namespace = 'jabber:client'
     name = 'message'
-    interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject',
-                      'mucroom', 'mucnick'))
-    sub_interfaces = set(('body', 'subject'))
+    namespace = 'jabber:client'
     plugin_attrib = name
-    types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat'))
+    interfaces = set(['type', 'to', 'from', 'id', 'body', 'subject',
+                      'mucroom', 'mucnick'])
+    sub_interfaces = set(['body', 'subject'])
+    lang_interfaces = sub_interfaces
+    types = set([None, 'normal', 'chat', 'headline', 'error', 'groupchat'])
 
     def get_type(self):
         """
-- 
cgit v1.2.3


From 9cec284947a957b2d335ff528d0c3c037f3a6dfe Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Fri, 22 Jun 2012 20:05:17 -0700
Subject: Mark presence status as language aware.

---
 sleekxmpp/stanza/presence.py | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py
index f2dd0968..7951f861 100644
--- a/sleekxmpp/stanza/presence.py
+++ b/sleekxmpp/stanza/presence.py
@@ -60,16 +60,17 @@ class Presence(RootStanza):
         set_priority -- Set the value of the <priority> element.
     """
 
-    namespace = 'jabber:client'
     name = 'presence'
-    interfaces = set(('type', 'to', 'from', 'id', 'show',
-                      'status', 'priority'))
-    sub_interfaces = set(('show', 'status', 'priority'))
+    namespace = 'jabber:client'
     plugin_attrib = name
-
-    types = set(('available', 'unavailable', 'error', 'probe', 'subscribe',
-                 'subscribed', 'unsubscribe', 'unsubscribed'))
-    showtypes = set(('dnd', 'chat', 'xa', 'away'))
+    interfaces = set(['type', 'to', 'from', 'id', 'show',
+                      'status', 'priority'])
+    sub_interfaces = set(['show', 'status', 'priority'])
+    lang_interfaces = set(['status'])
+
+    types = set(['available', 'unavailable', 'error', 'probe', 'subscribe',
+                 'subscribed', 'unsubscribe', 'unsubscribed'])
+    showtypes = set(['dnd', 'chat', 'xa', 'away'])
 
     def exception(self, e):
         """
-- 
cgit v1.2.3


From 82698672bb8818fce212cad6f7f98e890bc005e1 Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Fri, 22 Jun 2012 20:05:34 -0700
Subject: Add 'thread' and 'parent_thread' interfaces to message stanzas.

These values are perisisted across replies.
---
 sleekxmpp/stanza/message.py | 39 +++++++++++++++++++++++++++++++++++----
 1 file changed, 35 insertions(+), 4 deletions(-)

diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py
index 992d7b7b..02133682 100644
--- a/sleekxmpp/stanza/message.py
+++ b/sleekxmpp/stanza/message.py
@@ -7,7 +7,7 @@
 """
 
 from sleekxmpp.stanza.rootstanza import RootStanza
-from sleekxmpp.xmlstream import StanzaBase
+from sleekxmpp.xmlstream import StanzaBase, ET
 
 
 class Message(RootStanza):
@@ -58,10 +58,10 @@ class Message(RootStanza):
     namespace = 'jabber:client'
     plugin_attrib = name
     interfaces = set(['type', 'to', 'from', 'id', 'body', 'subject',
-                      'mucroom', 'mucnick'])
-    sub_interfaces = set(['body', 'subject'])
+                      'thread', 'parent_thread', 'mucroom', 'mucnick'])
+    sub_interfaces = set(['body', 'subject', 'thread'])
     lang_interfaces = sub_interfaces
-    types = set([None, 'normal', 'chat', 'headline', 'error', 'groupchat'])
+    types = set(['normal', 'chat', 'headline', 'error', 'groupchat'])
 
     def get_type(self):
         """
@@ -73,6 +73,31 @@ class Message(RootStanza):
         """
         return self._get_attr('type', 'normal')
 
+    def get_parent_thread(self):
+        """Return the message thread's parent thread."""
+        thread = self.xml.find('{%s}thread' % self.namespace)
+        if thread is not None:
+            return thread.attrib.get('parent', '')
+        return ''
+
+    def set_parent_thread(self, value):
+        """Add or change the message thread's parent thread."""
+        thread = self.xml.find('{%s}thread' % self.namespace)
+        if value:
+            if thread is None:
+                thread = ET.Element('{%s}thread' % self.namespace)
+                self.xml.append(thread)
+            thread.attrib['parent'] = value
+        else:
+            if thread is not None and 'parent' in thread.attrib:
+                del thread.attrib['parent']
+
+    def del_parent_thread(self):
+        """Delete the message thread's parent reference."""
+        thread = self.xml.find('{%s}thread' % self.namespace)
+        if thread is not None and 'parent' in thread.attrib:
+            del thread.attrib['parent']
+
     def chat(self):
         """Set the message type to 'chat'."""
         self['type'] = 'chat'
@@ -97,10 +122,16 @@ class Message(RootStanza):
             clear -- Indicates if existing content should be removed
                      before replying. Defaults to True.
         """
+        thread = self['thread']
+        parent = self['parent_thread']
+
         StanzaBase.reply(self, clear)
         if self['type'] == 'groupchat':
             self['to'] = self['to'].bare
 
+        self['thread'] = thread
+        self['parent_thread'] = parent
+
         del self['id']
 
         if body is not None:
-- 
cgit v1.2.3


From 69ddeceb491b0791d3b6ff4a5bb135c79cf490a0 Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Fri, 22 Jun 2012 21:13:30 -0700
Subject: Add support for XEP-0256: Last Activity in Presence

---
 setup.py                                    |  1 +
 sleekxmpp/plugins/__init__.py               |  1 +
 sleekxmpp/plugins/xep_0256/__init__.py      | 14 +++++++
 sleekxmpp/plugins/xep_0256/last_activity.py | 64 +++++++++++++++++++++++++++++
 4 files changed, 80 insertions(+)
 create mode 100644 sleekxmpp/plugins/xep_0256/__init__.py
 create mode 100644 sleekxmpp/plugins/xep_0256/last_activity.py

diff --git a/setup.py b/setup.py
index 71973ceb..4c22611f 100755
--- a/setup.py
+++ b/setup.py
@@ -93,6 +93,7 @@ packages     = [ 'sleekxmpp',
                  'sleekxmpp/plugins/xep_0224',
                  'sleekxmpp/plugins/xep_0231',
                  'sleekxmpp/plugins/xep_0249',
+                 'sleekxmpp/plugins/xep_0256',
                  'sleekxmpp/plugins/xep_0258',
                  'sleekxmpp/features',
                  'sleekxmpp/features/feature_mechanisms',
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index cc41280c..8464f0cb 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -56,5 +56,6 @@ __all__ = [
     'xep_0224',  # Attention
     'xep_0231',  # Bits of Binary
     'xep_0249',  # Direct MUC Invitations
+    'xep_0256',  # Last Activity in Presence
     'xep_0258',  # Security Labels in XMPP
 ]
diff --git a/sleekxmpp/plugins/xep_0256/__init__.py b/sleekxmpp/plugins/xep_0256/__init__.py
new file mode 100644
index 00000000..cfe712b7
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0256/__init__.py
@@ -0,0 +1,14 @@
+"""
+    SleekXMPP: The Sleek XMPP Library
+    Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+    This file is part of SleekXMPP.
+
+    See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.base import register_plugin
+
+from sleekxmpp.plugins.xep_0256.last_activity import XEP_0256
+
+
+register_plugin(XEP_0256)
diff --git a/sleekxmpp/plugins/xep_0256/last_activity.py b/sleekxmpp/plugins/xep_0256/last_activity.py
new file mode 100644
index 00000000..2938868d
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0256/last_activity.py
@@ -0,0 +1,64 @@
+"""
+    SleekXMPP: The Sleek XMPP Library
+    Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+    This file is part of SleekXMPP.
+
+    See the file LICENSE for copying permission.
+"""
+
+import logging
+
+from sleekxmpp import Presence
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.xmlstream import register_stanza_plugin
+
+from sleekxmpp.plugins.xep_0012 import stanza, LastActivity
+
+
+log = logging.getLogger(__name__)
+
+
+class XEP_0256(BasePlugin):
+
+    name = 'xep_0256'
+    description = 'XEP-0256: Last Activity in Presence'
+    dependencies = set(['xep_0012'])
+    stanza = stanza
+
+    def plugin_init(self):
+        self.auto_last_activity = self.config.get('auto_last_activity', False)
+
+        register_stanza_plugin(Presence, LastActivity)
+
+        self.xmpp.add_filter('out', self._initial_presence_activity)
+        self.xmpp.add_event_handler('connected', self._reset_presence_activity)
+
+        self._initial_presence = set()
+
+    def _reset_presence_activity(self, e):
+        self._initial_presence = set()
+
+    def _initial_presence_activity(self, stanza):
+        if isinstance(stanza, Presence):
+            use_last_activity = False
+
+            if self.auto_last_activity and  stanza['show'] in ('xa', 'away'):
+                use_last_activity = True
+
+            if stanza['from'] not in self._initial_presence:
+                self._initial_presence.add(stanza['from'])
+                use_last_activity = True
+
+            if use_last_activity:
+                plugin = self.xmpp['xep_0012']
+                try:
+                    result = plugin.api['get_last_activity'](stanza['from'],
+                                                             None,
+                                                             stanza['to'])
+                    seconds = result['last_activity']['seconds']
+                except XMPPError:
+                    seconds = None
+
+                if seconds is not None:
+                    stanza['last_activity']['seconds'] = seconds
+        return stanza
-- 
cgit v1.2.3


From 5027d00c109ed02b79e9c985077674f6dfd42766 Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Fri, 22 Jun 2012 21:25:37 -0700
Subject: Change packaging for XEP-0256 to just a single file.

---
 setup.py                                    |  1 -
 sleekxmpp/plugins/xep_0256.py               | 64 +++++++++++++++++++++++++++++
 sleekxmpp/plugins/xep_0256/__init__.py      | 14 -------
 sleekxmpp/plugins/xep_0256/last_activity.py | 64 -----------------------------
 4 files changed, 64 insertions(+), 79 deletions(-)
 create mode 100644 sleekxmpp/plugins/xep_0256.py
 delete mode 100644 sleekxmpp/plugins/xep_0256/__init__.py
 delete mode 100644 sleekxmpp/plugins/xep_0256/last_activity.py

diff --git a/setup.py b/setup.py
index 4c22611f..71973ceb 100755
--- a/setup.py
+++ b/setup.py
@@ -93,7 +93,6 @@ packages     = [ 'sleekxmpp',
                  'sleekxmpp/plugins/xep_0224',
                  'sleekxmpp/plugins/xep_0231',
                  'sleekxmpp/plugins/xep_0249',
-                 'sleekxmpp/plugins/xep_0256',
                  'sleekxmpp/plugins/xep_0258',
                  'sleekxmpp/features',
                  'sleekxmpp/features/feature_mechanisms',
diff --git a/sleekxmpp/plugins/xep_0256.py b/sleekxmpp/plugins/xep_0256.py
new file mode 100644
index 00000000..2938868d
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0256.py
@@ -0,0 +1,64 @@
+"""
+    SleekXMPP: The Sleek XMPP Library
+    Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+    This file is part of SleekXMPP.
+
+    See the file LICENSE for copying permission.
+"""
+
+import logging
+
+from sleekxmpp import Presence
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.xmlstream import register_stanza_plugin
+
+from sleekxmpp.plugins.xep_0012 import stanza, LastActivity
+
+
+log = logging.getLogger(__name__)
+
+
+class XEP_0256(BasePlugin):
+
+    name = 'xep_0256'
+    description = 'XEP-0256: Last Activity in Presence'
+    dependencies = set(['xep_0012'])
+    stanza = stanza
+
+    def plugin_init(self):
+        self.auto_last_activity = self.config.get('auto_last_activity', False)
+
+        register_stanza_plugin(Presence, LastActivity)
+
+        self.xmpp.add_filter('out', self._initial_presence_activity)
+        self.xmpp.add_event_handler('connected', self._reset_presence_activity)
+
+        self._initial_presence = set()
+
+    def _reset_presence_activity(self, e):
+        self._initial_presence = set()
+
+    def _initial_presence_activity(self, stanza):
+        if isinstance(stanza, Presence):
+            use_last_activity = False
+
+            if self.auto_last_activity and  stanza['show'] in ('xa', 'away'):
+                use_last_activity = True
+
+            if stanza['from'] not in self._initial_presence:
+                self._initial_presence.add(stanza['from'])
+                use_last_activity = True
+
+            if use_last_activity:
+                plugin = self.xmpp['xep_0012']
+                try:
+                    result = plugin.api['get_last_activity'](stanza['from'],
+                                                             None,
+                                                             stanza['to'])
+                    seconds = result['last_activity']['seconds']
+                except XMPPError:
+                    seconds = None
+
+                if seconds is not None:
+                    stanza['last_activity']['seconds'] = seconds
+        return stanza
diff --git a/sleekxmpp/plugins/xep_0256/__init__.py b/sleekxmpp/plugins/xep_0256/__init__.py
deleted file mode 100644
index cfe712b7..00000000
--- a/sleekxmpp/plugins/xep_0256/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-"""
-    SleekXMPP: The Sleek XMPP Library
-    Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
-    This file is part of SleekXMPP.
-
-    See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.plugins.base import register_plugin
-
-from sleekxmpp.plugins.xep_0256.last_activity import XEP_0256
-
-
-register_plugin(XEP_0256)
diff --git a/sleekxmpp/plugins/xep_0256/last_activity.py b/sleekxmpp/plugins/xep_0256/last_activity.py
deleted file mode 100644
index 2938868d..00000000
--- a/sleekxmpp/plugins/xep_0256/last_activity.py
+++ /dev/null
@@ -1,64 +0,0 @@
-"""
-    SleekXMPP: The Sleek XMPP Library
-    Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
-    This file is part of SleekXMPP.
-
-    See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Presence
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.xmlstream import register_stanza_plugin
-
-from sleekxmpp.plugins.xep_0012 import stanza, LastActivity
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0256(BasePlugin):
-
-    name = 'xep_0256'
-    description = 'XEP-0256: Last Activity in Presence'
-    dependencies = set(['xep_0012'])
-    stanza = stanza
-
-    def plugin_init(self):
-        self.auto_last_activity = self.config.get('auto_last_activity', False)
-
-        register_stanza_plugin(Presence, LastActivity)
-
-        self.xmpp.add_filter('out', self._initial_presence_activity)
-        self.xmpp.add_event_handler('connected', self._reset_presence_activity)
-
-        self._initial_presence = set()
-
-    def _reset_presence_activity(self, e):
-        self._initial_presence = set()
-
-    def _initial_presence_activity(self, stanza):
-        if isinstance(stanza, Presence):
-            use_last_activity = False
-
-            if self.auto_last_activity and  stanza['show'] in ('xa', 'away'):
-                use_last_activity = True
-
-            if stanza['from'] not in self._initial_presence:
-                self._initial_presence.add(stanza['from'])
-                use_last_activity = True
-
-            if use_last_activity:
-                plugin = self.xmpp['xep_0012']
-                try:
-                    result = plugin.api['get_last_activity'](stanza['from'],
-                                                             None,
-                                                             stanza['to'])
-                    seconds = result['last_activity']['seconds']
-                except XMPPError:
-                    seconds = None
-
-                if seconds is not None:
-                    stanza['last_activity']['seconds'] = seconds
-        return stanza
-- 
cgit v1.2.3


From b11e1ee92d4c81fc3943c376477aa8be2a8aa0e6 Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Fri, 22 Jun 2012 21:26:25 -0700
Subject: Add meta plugin for XEP-0270, 2010 compliance suite.

Registering this plugin will load the plugins required for advanced
client compliance status.
---
 sleekxmpp/plugins/__init__.py |  1 +
 sleekxmpp/plugins/xep_0270.py | 17 +++++++++++++++++
 2 files changed, 18 insertions(+)
 create mode 100644 sleekxmpp/plugins/xep_0270.py

diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index 8464f0cb..5aa48a47 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -58,4 +58,5 @@ __all__ = [
     'xep_0249',  # Direct MUC Invitations
     'xep_0256',  # Last Activity in Presence
     'xep_0258',  # Security Labels in XMPP
+    'xep_0270',  # XMPP Compliance Suites 2010
 ]
diff --git a/sleekxmpp/plugins/xep_0270.py b/sleekxmpp/plugins/xep_0270.py
new file mode 100644
index 00000000..41ccd73b
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0270.py
@@ -0,0 +1,17 @@
+"""
+    SleekXMPP: The Sleek XMPP Library
+    Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+    This file is part of SleekXMPP.
+
+    See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins import BasePlugin
+
+
+class XEP_0270(BasePlugin):
+
+    name = 'xep_0270'
+    description = 'XEP-0270: XMPP Compliance Suites 2010'
+    dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
+                        'xep_0163', 'xep_0045', 'xep_0085'])
-- 
cgit v1.2.3


From e4911e93919e733e1a3a0c1db4c6a32b87fef04a Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Fri, 22 Jun 2012 21:50:43 -0700
Subject: Add meta plugin for XEP-0302 for the 2012 compliance suite.

There are still a few remaining items in the RFCs to add support for,
but the current plugin support matches the advanced client profile.
---
 sleekxmpp/plugins/__init__.py |  1 +
 sleekxmpp/plugins/xep_0302.py | 18 ++++++++++++++++++
 2 files changed, 19 insertions(+)
 create mode 100644 sleekxmpp/plugins/xep_0302.py

diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index 5aa48a47..1466a274 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -59,4 +59,5 @@ __all__ = [
     'xep_0256',  # Last Activity in Presence
     'xep_0258',  # Security Labels in XMPP
     'xep_0270',  # XMPP Compliance Suites 2010
+    'xep_0302',  # XMPP Compliance Suites 2012
 ]
diff --git a/sleekxmpp/plugins/xep_0302.py b/sleekxmpp/plugins/xep_0302.py
new file mode 100644
index 00000000..83ab9a35
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0302.py
@@ -0,0 +1,18 @@
+"""
+    SleekXMPP: The Sleek XMPP Library
+    Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+    This file is part of SleekXMPP.
+
+    See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins import BasePlugin
+
+
+class XEP_0302(BasePlugin):
+
+    name = 'xep_0302'
+    description = 'XEP-0302: XMPP Compliance Suites 2012'
+    dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
+                        'xep_0163', 'xep_0045', 'xep_0085',
+                        'xep_0184', 'xep_0198'])
-- 
cgit v1.2.3


From c2c7cc032b38d8a7a293467a8c2ff7380cbc756f Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Fri, 22 Jun 2012 21:58:50 -0700
Subject: Fix plugin registration for single file plugins.

---
 sleekxmpp/plugins/xep_0256.py | 5 ++++-
 sleekxmpp/plugins/xep_0270.py | 5 ++++-
 sleekxmpp/plugins/xep_0302.py | 5 ++++-
 3 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/sleekxmpp/plugins/xep_0256.py b/sleekxmpp/plugins/xep_0256.py
index 2938868d..265a5da8 100644
--- a/sleekxmpp/plugins/xep_0256.py
+++ b/sleekxmpp/plugins/xep_0256.py
@@ -9,7 +9,7 @@
 import logging
 
 from sleekxmpp import Presence
-from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.plugins import BasePlugin, register_plugin
 from sleekxmpp.xmlstream import register_stanza_plugin
 
 from sleekxmpp.plugins.xep_0012 import stanza, LastActivity
@@ -62,3 +62,6 @@ class XEP_0256(BasePlugin):
                 if seconds is not None:
                     stanza['last_activity']['seconds'] = seconds
         return stanza
+
+
+register_plugin(XEP_0256)
diff --git a/sleekxmpp/plugins/xep_0270.py b/sleekxmpp/plugins/xep_0270.py
index 41ccd73b..eadd45ad 100644
--- a/sleekxmpp/plugins/xep_0270.py
+++ b/sleekxmpp/plugins/xep_0270.py
@@ -6,7 +6,7 @@
     See the file LICENSE for copying permission.
 """
 
-from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.plugins import BasePlugin, register_plugin
 
 
 class XEP_0270(BasePlugin):
@@ -15,3 +15,6 @@ class XEP_0270(BasePlugin):
     description = 'XEP-0270: XMPP Compliance Suites 2010'
     dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
                         'xep_0163', 'xep_0045', 'xep_0085'])
+
+
+register_plugin(XEP_0270)
diff --git a/sleekxmpp/plugins/xep_0302.py b/sleekxmpp/plugins/xep_0302.py
index 83ab9a35..dee60f91 100644
--- a/sleekxmpp/plugins/xep_0302.py
+++ b/sleekxmpp/plugins/xep_0302.py
@@ -6,7 +6,7 @@
     See the file LICENSE for copying permission.
 """
 
-from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.plugins import BasePlugin, register_plugin
 
 
 class XEP_0302(BasePlugin):
@@ -16,3 +16,6 @@ class XEP_0302(BasePlugin):
     dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
                         'xep_0163', 'xep_0045', 'xep_0085',
                         'xep_0184', 'xep_0198'])
+
+
+register_plugin(XEP_0302)
-- 
cgit v1.2.3


From 85ef2d8d0bcc92cd20d857d01bcf1bba56bc8edf Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Fri, 22 Jun 2012 22:59:15 -0700
Subject: Add support for reconnecting based on see-other-host stream errors.

---
 sleekxmpp/basexmpp.py | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
index aae80168..275275d1 100644
--- a/sleekxmpp/basexmpp.py
+++ b/sleekxmpp/basexmpp.py
@@ -134,6 +134,7 @@ class BaseXMPP(XMLStream):
             Callback('Presence',
                      MatchXPath("{%s}presence" % self.default_ns),
                      self._handle_presence))
+
         self.register_handler(
             Callback('Stream Error',
                      MatchXPath("{%s}error" % self.stream_ns),
@@ -658,6 +659,27 @@ class BaseXMPP(XMLStream):
     def _handle_stream_error(self, error):
         self.event('stream_error', error)
 
+        if error['condition'] == 'see-other-host':
+            other_host = error['see_other_host']
+
+            host = other_host
+            port = 5222
+
+            if '[' in other_host and ']' in other_host:
+                host = other_host.split(']')[0][1:]
+            elif ':' in other_host:
+                host = other_host.split(':')[0]
+
+            port_sec = other_host.split(']')[-1]
+            if ':' in port_sec:
+                port = int(port_sec.split(':')[1])
+
+            self.address = (host, port)
+            self.default_domain = host
+            self.dns_records = None
+            self.reconnect_delay = None
+            self.reconnect()
+
     def _handle_message(self, msg):
         """Process incoming message stanzas."""
         if not self.is_component and not msg['to'].bare:
-- 
cgit v1.2.3