Changeset 173:6b0eb01b5744
- Timestamp:
- May 9, 2012, 2:24:28 PM (10 years ago)
- Branch:
- default
- Files:
-
- 1 added
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
doc/xmppim.rst
r172 r173 32 32 :language: python 33 33 :linenos: 34 35 Roster versioning 36 ^^^^^^^^^^^^^^^^^ 37 38 Some XMPP servers support roster versioning. A client can keep a cache of the 39 roster by requesting it and applying changes as roster pushes come in. Each 40 version of the roster is marked with a version identifier. This can be used 41 to request the roster upon reconnect. The server can then choose to send the 42 difference between the requested and current version as roster pushed, 43 instead of returning the complete roster. 44 45 When no roster was cached by the client, yet, a client passes the empty 46 string (``''``) to ``getRoster`` to bootstrap the process. 47 48 This example will force a reconnect 15 seconds after authentication. 49 50 .. literalinclude:: listings/xmppim/roster_client_versioning.py 51 :language: python 52 :linenos: -
wokkel/test/test_xmppim.py
r172 r173 838 838 self.assertEqual(JID('this@example.org'), request.sender) 839 839 self.assertEqual(None, request.item) 840 self.assertEqual(None, request.version) 840 841 841 842 … … 855 856 self.assertNotIdentical(None, request.item) 856 857 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) 857 886 858 887 … … 904 933 905 934 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 906 955 907 956 class RosterClientProtocolTest(unittest.TestCase, TestableRequestHandlerMixin): … … 950 999 def cb(roster): 951 1000 self.assertIn(JID('user@example.org'), roster) 1001 self.assertIdentical(None, getattr(roster, 'version')) 952 1002 953 1003 d = self.service.getRoster() … … 960 1010 self.assertNotIdentical(None, iq.query) 961 1011 self.assertEqual(NS_ROSTER, iq.query.uri) 1012 self.assertFalse(iq.query.hasAttribute('ver')) 962 1013 963 1014 # Fake successful response … … 967 1018 item['jid'] = 'user@example.org' 968 1019 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') 969 1066 d.callback(response) 970 1067 return d -
wokkel/xmppim.py
r172 r173 749 749 @ivar item: Roster item to be set or pushed. 750 750 @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} 751 757 """ 752 758 item = None 759 version = None 753 760 754 761 def parseRequest(self, element): 762 self.version = element.getAttribute('ver') 763 755 764 for child in element.elements(NS_ROSTER, 'item'): 756 765 self.item = RosterItem.fromElement(child) … … 761 770 element = Request.toElement(self) 762 771 query = element.addElement((NS_ROSTER, 'query')) 772 if self.version is not None: 773 query['ver'] = self.version 763 774 if self.item: 764 775 query.addChild(self.item.toElement()) … … 775 786 result in a C{'service-unavailable'} error being sent in return. 776 787 """ 788 789 790 791 class 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 777 804 778 805 … … 797 824 pushes from untrusted senders. 798 825 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 799 833 @cvar allowAnySender: Flag to allow roster pushes from any sender. 800 834 C{False} by default. … … 810 844 811 845 812 def getRoster(self ):846 def getRoster(self, version=None): 813 847 """ 814 848 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} 815 872 816 873 @return: Roster as a mapping from L{JID} to L{RosterItem}. … … 819 876 820 877 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 828 887 829 888 request = RosterRequest(stanzaType='get') 889 request.version = version 830 890 d = self.request(request) 831 891 d.addCallback(processRoster)
Note: See TracChangeset
for help on using the changeset viewer.