From 8e9b3d0760f7cfbce0d480cb61050a9b78746f8c Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Fri, 13 May 2011 15:28:47 -0400
Subject: Ensure that the XEP-0086 plugin is loaded.

Since the XEP-0086 plugin auto adds error code values,
it must be reliably loaded or unloaded when certain tests
are run so that stanzas may be matched. In this case, we
ensure that the plugin is used.
---
 tests/test_stanza_error.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tests/test_stanza_error.py b/tests/test_stanza_error.py
index 5eecfee9..a41bf4bf 100644
--- a/tests/test_stanza_error.py
+++ b/tests/test_stanza_error.py
@@ -3,6 +3,11 @@ from sleekxmpp.test import *
 
 class TestErrorStanzas(SleekTest):
 
+    def setUp(self):
+        # Ensure that the XEP-0086 plugin has been loaded.
+        self.stream_start()
+        self.stream_close()
+
     def testSetup(self):
         """Test setting initial values in error stanza."""
         msg = self.Message()
-- 
cgit v1.2.3


From 9f1648328f17b608651989606b9cf2636cdcfbec Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Fri, 20 May 2011 12:56:00 -0400
Subject: Resolve timeout errors for get_roster.

See issue #89

Using get_roster will now return the same types of values as
Iq.send. If a timeout occurs, then the event 'roster_timeout'
will be fired. A successful call to get_roster will also
raise the 'roster_received' event.

To ensure that the get_roster call was successful, here
is a pattern to follow:

    def __init__(self, ...):
        ...
        self.add_event_handler('session_start', self.session_start)
        self.add_event_handler('roster_timeout', self.roster_timeout)
        self.add_event_handler('roster_received', self.roster_received)

    def session_start(self, e):
        self.send_presence()
        self.get_roster()

    def roster_timeout(self, e):
        # Optionally increase the timeout period
        self.get_roster(timeout=self.response_timeout * 2)

    def roster_received(self, iq):
        # Do stuff, roster has been initialized.
        ...
---
 sleekxmpp/clientxmpp.py     | 53 ++++++++++++++++++++++++-----
 tests/test_stream_roster.py | 81 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 126 insertions(+), 8 deletions(-)

diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py
index 20cc9417..c518a4ce 100644
--- a/sleekxmpp/clientxmpp.py
+++ b/sleekxmpp/clientxmpp.py
@@ -206,7 +206,8 @@ class ClientXMPP(BaseXMPP):
                                          pointer,
                                          breaker))
 
-    def update_roster(self, jid, name=None, subscription=None, groups=[]):
+    def update_roster(self, jid, name=None, subscription=None, groups=[],
+                            block=True, timeout=None, callback=None):
         """
         Add or change a roster item.
 
@@ -217,12 +218,24 @@ class ClientXMPP(BaseXMPP):
                             'to', 'from', 'both', or 'none'. If set
                             to 'remove', the entry will be deleted.
             groups       -- The roster groups that contain this item.
+            block        -- Specify if the roster request will block
+                            until a response is received, or a timeout
+                            occurs. Defaults to True.
+            timeout      -- The length of time (in seconds) to wait
+                            for a response before continuing if blocking
+                            is used. Defaults to self.response_timeout.
+            callback     -- Optional reference to a stream handler function.
+                            Will be executed when the roster is received.
+                            Implies block=False.
         """
-        iq = self.Iq()._set_stanza_values({'type': 'set'})
+        iq = self.Iq()
+        iq['type'] = 'set'
         iq['roster']['items'] = {jid: {'name': name,
                                        'subscription': subscription,
                                        'groups': groups}}
-        response = iq.send()
+        response = iq.send(block, timeout, callback)
+        if response in [False, None]:
+            return response
         return response['type'] == 'result'
 
     def del_roster_item(self, jid):
@@ -235,11 +248,33 @@ class ClientXMPP(BaseXMPP):
         """
         return self.update_roster(jid, subscription='remove')
 
-    def get_roster(self):
-        """Request the roster from the server."""
-        iq = self.Iq()._set_stanza_values({'type': 'get'}).enable('roster')
-        response = iq.send()
-        self._handle_roster(response, request=True)
+    def get_roster(self, block=True, timeout=None, callback=None):
+        """
+        Request the roster from the server.
+
+        Arguments:
+            block    -- Specify if the roster request will block until a
+                        response is received, or a timeout occurs.
+                        Defaults to True.
+            timeout  -- The length of time (in seconds) to wait for a response
+                        before continuing if blocking is used.
+                        Defaults to self.response_timeout.
+            callback -- Optional reference to a stream handler function. Will
+                        be executed when the roster is received.
+                        Implies block=False.
+        """
+        iq = self.Iq()
+        iq['type'] = 'get'
+        iq.enable('roster')
+        response = iq.send(block, timeout, callback)
+
+        if response == False:
+            self.event('roster_timeout')
+
+        if response in [False, None] or not isinstance(response, Iq):
+            return response
+        else:
+            return self._handle_roster(response, request=True)
 
     def _handle_stream_features(self, features):
         """
@@ -431,12 +466,14 @@ class ClientXMPP(BaseXMPP):
                                         'presence': {},
                                         'in_roster': True}
                 self.roster[jid].update(iq['roster']['items'][jid])
+            self.event('roster_received', iq)
 
         self.event("roster_update", iq)
         if iq['type'] == 'set':
             iq.reply()
             iq.enable('roster')
             iq.send()
+        return True
 
 
 # To comply with PEP8, method names now use underscores.
diff --git a/tests/test_stream_roster.py b/tests/test_stream_roster.py
index 165a8bc9..731d1145 100644
--- a/tests/test_stream_roster.py
+++ b/tests/test_stream_roster.py
@@ -16,6 +16,13 @@ class TestStreamRoster(SleekTest):
         self.stream_start(mode='client')
         self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.")
 
+        events = []
+
+        def roster_received(iq):
+            events.append('roster_received')
+
+        self.xmpp.add_event_handler('roster_received', roster_received)
+
         # Since get_roster blocks, we need to run it in a thread.
         t = threading.Thread(name='get_roster', target=self.xmpp.get_roster)
         t.start()
@@ -41,6 +48,9 @@ class TestStreamRoster(SleekTest):
         # Wait for get_roster to return.
         t.join()
 
+        # Give the event queue time to process.
+        time.sleep(.1)
+
         roster = {'user@localhost': {'name': 'User',
                                      'subscription': 'both',
                                      'groups': ['Friends', 'Examples'],
@@ -48,11 +58,20 @@ class TestStreamRoster(SleekTest):
                                      'in_roster': True}}
         self.failUnless(self.xmpp.roster == roster,
                 "Unexpected roster values: %s" % self.xmpp.roster)
+        self.failUnless('roster_received' in events,
+                "Roster received event not triggered: %s" % events)
 
     def testRosterSet(self):
         """Test handling pushed roster updates."""
         self.stream_start(mode='client')
         self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.")
+        events = []
+
+        def roster_update(e):
+            events.append('roster_update')
+
+        self.xmpp.add_event_handler('roster_update', roster_update)
+
 
         self.recv("""
           <iq type="set" id="1">
@@ -72,6 +91,9 @@ class TestStreamRoster(SleekTest):
           </iq>
         """)
 
+        # Give the event queue time to process.
+        time.sleep(.1)
+
         roster = {'user@localhost': {'name': 'User',
                                      'subscription': 'both',
                                      'groups': ['Friends', 'Examples'],
@@ -79,7 +101,66 @@ class TestStreamRoster(SleekTest):
                                      'in_roster': True}}
         self.failUnless(self.xmpp.roster == roster,
                 "Unexpected roster values: %s" % self.xmpp.roster)
+        self.failUnless('roster_update' in events,
+                "Roster updated event not triggered: %s" % events)
+
+    def testRosterTimeout(self):
+        """Test handling a timed out roster request."""
+        self.stream_start()
+        events = []
+
+        def roster_timeout(event):
+            events.append('roster_timeout')
+
+        self.xmpp.add_event_handler('roster_timeout', roster_timeout)
+        self.xmpp.get_roster(timeout=0)
+
+        # Give the event queue time to process.
+        time.sleep(.1)
+
+        self.failUnless(events == ['roster_timeout'],
+                 "Roster timeout event not triggered: %s." % events)
+
+    def testRosterCallback(self):
+        """Test handling a roster request callback."""
+        self.stream_start()
+        events = []
+
+        def roster_callback(iq):
+            events.append('roster_callback')
+
+        # Since get_roster blocks, we need to run it in a thread.
+        t = threading.Thread(name='get_roster',
+                             target=self.xmpp.get_roster,
+                             kwargs={'callback': roster_callback})
+        t.start()
+
+        self.send("""
+          <iq type="get" id="1">
+            <query xmlns="jabber:iq:roster" />
+          </iq>
+        """)
+        self.recv("""
+          <iq type="result" id="1">
+            <query xmlns="jabber:iq:roster">
+              <item jid="user@localhost"
+                    name="User"
+                    subscription="both">
+                <group>Friends</group>
+                <group>Examples</group>
+              </item>
+            </query>
+          </iq>
+        """)
+
+        # Wait for get_roster to return.
+        t.join()
+
+        # Give the event queue time to process.
+        time.sleep(.1)
 
+        self.failUnless(events == ['roster_callback'],
+                 "Roster timeout event not triggered: %s." % events)
 
 
 
-- 
cgit v1.2.3