Changeset 173:6b0eb01b5744 for wokkel


Ignore:
Timestamp:
May 9, 2012, 2:24:28 PM (8 years ago)
Author:
Ralph Meijer <ralphm@…>
Branch:
default
Message:

Add support for roster versioning.

Location:
wokkel
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • wokkel/test/test_xmppim.py

    r172 r173  
    838838        self.assertEqual(JID('this@example.org'), request.sender)
    839839        self.assertEqual(None, request.item)
     840        self.assertEqual(None, request.version)
    840841
    841842
     
    855856        self.assertNotIdentical(None, request.item)
    856857        self.assertEqual(JID('user@example.org'), request.item.entity)
     858
     859
     860    def test_fromElementVersion(self):
     861        """
     862        If a ver attribute is present, put it in the request version.
     863        """
     864        xml = """
     865            <iq type='set' to='this@example.org/Home' from='this@example.org'>
     866              <query xmlns='jabber:iq:roster' ver='ver72'>
     867                <item jid='user@example.org'/>
     868              </query>
     869            </iq>
     870        """
     871        request = xmppim.RosterRequest.fromElement(parseXml(xml))
     872        self.assertEqual('ver72', request.version)
     873
     874
     875    def test_fromElementVersionEmpty(self):
     876        """
     877        The ver attribute may be empty.
     878        """
     879        xml = """
     880            <iq type='get' to='this@example.org/Home' from='this@example.org'>
     881              <query xmlns='jabber:iq:roster' ver=''/>
     882            </iq>
     883        """
     884        request = xmppim.RosterRequest.fromElement(parseXml(xml))
     885        self.assertEqual('', request.version)
    857886
    858887
     
    904933
    905934
     935    def test_toElementVersion(self):
     936        """
     937        If the roster version is set, a 'ver' attribute is added.
     938        """
     939        request = xmppim.RosterRequest()
     940        request.version = 'ver72'
     941        element = request.toElement()
     942        self.assertEqual('ver72', element.query.getAttribute('ver'))
     943
     944
     945    def test_toElementVersionEmpty(self):
     946        """
     947        If the roster version is the empty string, it should add 'ver', too.
     948        """
     949        request = xmppim.RosterRequest()
     950        request.version = ''
     951        element = request.toElement()
     952        self.assertEqual('', element.query.getAttribute('ver'))
     953
     954
    906955
    907956class RosterClientProtocolTest(unittest.TestCase, TestableRequestHandlerMixin):
     
    950999        def cb(roster):
    9511000            self.assertIn(JID('user@example.org'), roster)
     1001            self.assertIdentical(None, getattr(roster, 'version'))
    9521002
    9531003        d = self.service.getRoster()
     
    9601010        self.assertNotIdentical(None, iq.query)
    9611011        self.assertEqual(NS_ROSTER, iq.query.uri)
     1012        self.assertFalse(iq.query.hasAttribute('ver'))
    9621013
    9631014        # Fake successful response
     
    9671018        item['jid'] = 'user@example.org'
    9681019
     1020        d.callback(response)
     1021        return d
     1022
     1023
     1024    def test_getRosterVer(self):
     1025        """
     1026        A request for the roster with version passes the version on.
     1027        """
     1028        def cb(roster):
     1029            self.assertEqual('ver96', getattr(roster, 'version'))
     1030
     1031        d = self.service.getRoster(version='ver72')
     1032        d.addCallback(cb)
     1033
     1034        # Inspect outgoing iq request
     1035
     1036        iq = self.stub.output[-1]
     1037        self.assertEqual('ver72', iq.query.getAttribute('ver'))
     1038
     1039        # Fake successful response
     1040        response = toResponse(iq, 'result')
     1041        query = response.addElement((NS_ROSTER, 'query'))
     1042        query['ver'] = 'ver96'
     1043        item = query.addElement('item')
     1044        item['jid'] = 'user@example.org'
     1045
     1046        d.callback(response)
     1047        return d
     1048
     1049
     1050    def test_getRosterVerEmptyResult(self):
     1051        """
     1052        An empty response is returned as None.
     1053        """
     1054        def cb(response):
     1055            self.assertIdentical(None, response)
     1056
     1057        d = self.service.getRoster(version='ver72')
     1058        d.addCallback(cb)
     1059
     1060        # Inspect outgoing iq request
     1061
     1062        iq = self.stub.output[-1]
     1063
     1064        # Fake successful response
     1065        response = toResponse(iq, 'result')
    9691066        d.callback(response)
    9701067        return d
  • wokkel/xmppim.py

    r172 r173  
    749749    @ivar item: Roster item to be set or pushed.
    750750    @type item: L{RosterItem}.
     751
     752    @ivar version: Roster version identifier for roster pushes and
     753        retrieving the roster as a delta from a known cached version. This
     754        should only be set if the recipient is known to support roster
     755        versioning.
     756    @type version: C{unicode}
    751757    """
    752758    item = None
     759    version = None
    753760
    754761    def parseRequest(self, element):
     762        self.version = element.getAttribute('ver')
     763
    755764        for child in element.elements(NS_ROSTER, 'item'):
    756765            self.item = RosterItem.fromElement(child)
     
    761770        element = Request.toElement(self)
    762771        query = element.addElement((NS_ROSTER, 'query'))
     772        if self.version is not None:
     773            query['ver'] = self.version
    763774        if self.item:
    764775            query.addChild(self.item.toElement())
     
    775786    result in a C{'service-unavailable'} error being sent in return.
    776787    """
     788
     789
     790
     791class Roster(dict):
     792    """
     793    In-memory roster container.
     794
     795    This provides a roster as a mapping from L{JID} to L{RosterItem}. If
     796    roster versioning is used, the C{version} attribute holds the version
     797    identifier for this version of the roster.
     798
     799    @ivar version: Roster version identifier.
     800    @type version: C{unicode}.
     801    """
     802
     803    version = None
    777804
    778805
     
    797824    pushes from untrusted senders.
    798825
     826    If roster versioning is supported by the server, the roster and
     827    subsequent pushes are annotated with a version identifier. This can be
     828    used to cache the roster on the client side. Upon reconnect, the client
     829    can request the roster with the version identifier of the cached version.
     830    The server may then choose to only send roster pushes for the changes
     831    since that version, instead of a complete roster.
     832
    799833    @cvar allowAnySender: Flag to allow roster pushes from any sender.
    800834        C{False} by default.
     
    810844
    811845
    812     def getRoster(self):
     846    def getRoster(self, version=None):
    813847        """
    814848        Retrieve contact list.
     849
     850        The returned deferred fires with the result of the roster request as
     851        L{Roster}, a mapping from contact JID to L{RosterItem}.
     852
     853        If roster versioning is supported, the recipient responds with either
     854        a the complete roster or with an empty result. In case of complete
     855        roster, the L{Roster} is annotated with a C{version} attribute that
     856        holds the version identifier for this version of the roster. This
     857        identifier should be used for caching.
     858
     859        If the recipient responds with an empty result, the returned deferred
     860        fires with C{None}. This indicates that any roster modifications
     861        since C{version} will be sent as roster pushes.
     862
     863        Note that the empty result (C{None}) is different from an empty
     864        roster (L{Roster} with no items).
     865
     866        @param version: Optional version identifier of the last cashed
     867            version of the roster. This shall only be set if the recipient is
     868            known to support roster versioning. If there is no (valid) cached
     869            version of the roster, but roster versioning is desired,
     870            C{version} should be set to the empty string (C{u''}).
     871        @type version: C{unicode}
    815872
    816873        @return: Roster as a mapping from L{JID} to L{RosterItem}.
     
    819876
    820877        def processRoster(result):
    821             roster = {}
    822             for element in domish.generateElementsQNamed(result.query.children,
    823                                                          'item', NS_ROSTER):
    824                 item = RosterItem.fromElement(element)
    825                 roster[item.entity] = item
    826 
    827             return roster
     878            if result.query is not None:
     879                roster = Roster()
     880                roster.version = result.query.getAttribute('ver')
     881                for element in result.query.elements(NS_ROSTER, 'item'):
     882                    item = RosterItem.fromElement(element)
     883                    roster[item.entity] = item
     884                return roster
     885            else:
     886                return None
    828887
    829888        request = RosterRequest(stanzaType='get')
     889        request.version = version
    830890        d = self.request(request)
    831891        d.addCallback(processRoster)
Note: See TracChangeset for help on using the changeset viewer.