Mercurial > sat_tmp
comparison wokkel/rsm.py @ 0:09e7c32a6a00
use sat.tmp.wokkel as a buffer module until the changes are integrated to wokkel
author | souliane <souliane@mailoo.org> |
---|---|
date | Mon, 15 Dec 2014 12:46:58 +0100 |
parents | |
children | 9d35f88168a1 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:09e7c32a6a00 |
---|---|
1 # -*- test-case-name: wokkel.test.test_rsm -*- | |
2 # | |
3 # Copyright (c) Adrien Cossa. | |
4 # See LICENSE for details. | |
5 | |
6 """ | |
7 XMPP Result Set Management protocol. | |
8 | |
9 This protocol is specified in | |
10 U{XEP-0059<http://xmpp.org/extensions/xep-0059.html>}. | |
11 """ | |
12 | |
13 from twisted.python import log | |
14 from twisted.words.xish import domish | |
15 | |
16 import pubsub | |
17 import copy | |
18 | |
19 | |
20 # RSM namespace | |
21 NS_RSM = 'http://jabber.org/protocol/rsm' | |
22 | |
23 | |
24 class RSMRequest(): | |
25 """ | |
26 A Result Set Management request. | |
27 | |
28 @ivar max: limit on the number of retrieved items. | |
29 @itype max: C{int} or C{unicode} | |
30 | |
31 @ivar index: starting index of the requested page. | |
32 @itype index: C{int} or C{unicode} | |
33 | |
34 @ivar after: ID of the element immediately preceding the page. | |
35 @itype after: C{unicode} | |
36 | |
37 @ivar before: ID of the element immediately following the page. | |
38 @itype before: C{unicode} | |
39 """ | |
40 | |
41 max = 10 | |
42 index = None | |
43 after = None | |
44 before = None | |
45 | |
46 def __init__(self, max=None, index=None, after=None, before=None): | |
47 if max is not None: | |
48 max = int(max) | |
49 assert(max >= 0) | |
50 self.max = max | |
51 | |
52 if index is not None: | |
53 assert(after is None and before is None) | |
54 index = int(index) | |
55 assert(index >= 0) | |
56 self.index = index | |
57 | |
58 if after is not None: | |
59 assert(before is None) | |
60 assert(isinstance(after, unicode)) | |
61 self.after = after | |
62 | |
63 if before is not None: | |
64 assert(isinstance(before, unicode)) | |
65 self.before = before | |
66 | |
67 @classmethod | |
68 def parse(cls, element): | |
69 """Parse the given request element. | |
70 | |
71 @param element: request containing a set element. | |
72 @type element: L{domish.Element} | |
73 | |
74 @return: RSMRequest instance. | |
75 @rtype: L{RSMRequest} | |
76 """ | |
77 try: | |
78 set_elt = domish.generateElementsQNamed(element.elements(), | |
79 name="set", | |
80 uri=NS_RSM).next() | |
81 except StopIteration: | |
82 return None | |
83 | |
84 request = RSMRequest() | |
85 for elt in list(set_elt.elements()): | |
86 if elt.name in ('before', 'after'): | |
87 setattr(request, elt.name, ''.join(elt.children)) | |
88 elif elt.name in ('max', 'index'): | |
89 setattr(request, elt.name, int(''.join(elt.children))) | |
90 | |
91 if request.max is None: | |
92 log.err("RSM request is missing its 'max' element!") | |
93 | |
94 return request | |
95 | |
96 def render(self, element=None): | |
97 """Render a RSM page request, eventually embed it in the given element. | |
98 | |
99 @param element: request element. | |
100 @type element: L{domish.Element} | |
101 | |
102 @return: RSM request element. | |
103 @rtype: L{domish.Element} | |
104 """ | |
105 if element and element.name == 'pubsub' and hasattr(element, 'items'): | |
106 element.items.attributes['max_items'] = unicode(self.max) | |
107 | |
108 set_elt = domish.Element((NS_RSM, 'set')) | |
109 set_elt.addElement('max').addContent(unicode(self.max)) | |
110 | |
111 if self.index is not None: | |
112 set_elt.addElement('index').addContent(unicode(self.index)) | |
113 | |
114 if self.before is not None: | |
115 if self.before == '': # request the last page | |
116 set_elt.addElement('before') | |
117 else: | |
118 set_elt.addElement('before').addContent(self.before) | |
119 | |
120 if self.after is not None: | |
121 set_elt.addElement('after').addContent(self.after) | |
122 | |
123 if element: | |
124 element.addChild(set_elt) | |
125 | |
126 return set_elt | |
127 | |
128 | |
129 class RSMResponse(): | |
130 """ | |
131 A Result Set Management response. | |
132 | |
133 @ivar count: total number of items. | |
134 @itype count: C{int} | |
135 | |
136 @ivar index: starting index of the returned page. | |
137 @itype index: C{int} | |
138 | |
139 @ivar first: ID of the first element of the returned page. | |
140 @itype first: C{unicode} | |
141 | |
142 @ivar last: ID of the last element of the returned page. | |
143 @itype last: C{unicode} | |
144 """ | |
145 | |
146 count = 0 | |
147 index = None | |
148 first = None | |
149 last = None | |
150 | |
151 def __init__(self, count=None, index=None, first=None, last=None): | |
152 if count is not None: | |
153 assert(isinstance(count, int) and count >= 0) | |
154 self.count = count | |
155 | |
156 if index is not None: | |
157 assert(isinstance(index, int) and index >= 0) | |
158 self.index = index | |
159 assert(isinstance(first, unicode)) | |
160 self.first = first | |
161 assert(isinstance(last, unicode)) | |
162 self.last = last | |
163 else: | |
164 assert(first is None and last is None) | |
165 | |
166 @classmethod | |
167 def parse(cls, element): | |
168 """Parse the given response element. | |
169 | |
170 @param element: response element. | |
171 @type element: L{domish.Element} | |
172 | |
173 @return: RSMResponse instance. | |
174 @rtype: L{RSMResponse} | |
175 """ | |
176 try: | |
177 set_elt = domish.generateElementsQNamed(element.elements(), | |
178 name="set", | |
179 uri=NS_RSM).next() | |
180 except StopIteration: | |
181 return None | |
182 | |
183 response = RSMResponse() | |
184 for elt in list(set_elt.elements()): | |
185 if elt.name in ('first', 'last'): | |
186 setattr(response, elt.name, ''.join(elt.children)) | |
187 if elt.name == 'first': | |
188 response.index = int(elt.getAttribute("index")) | |
189 elif elt.name == 'count': | |
190 response.count = int(''.join(elt.children)) | |
191 | |
192 if response.count is None: | |
193 log.err("RSM response is missing its 'count' element!") | |
194 | |
195 return response | |
196 | |
197 def render(self, parent=None): | |
198 """Render a RSM page response, eventually embed it in the given element. | |
199 | |
200 @param element: response element. | |
201 @type element: L{domish.Element} | |
202 | |
203 @return: RSM request element. | |
204 @rtype: L{domish.Element} | |
205 """ | |
206 set_elt = domish.Element((NS_RSM, 'set')) | |
207 set_elt.addElement('count').addContent(unicode(self.count)) | |
208 | |
209 if self.index is not None: | |
210 first_elt = set_elt.addElement('first') | |
211 first_elt.addContent(self.first) | |
212 first_elt['index'] = unicode(self.index) | |
213 | |
214 set_elt.addElement('last').addContent(self.last) | |
215 | |
216 if parent: | |
217 parent.addChild(set_elt) | |
218 | |
219 return set_elt | |
220 | |
221 def toDict(self): | |
222 """Return a dict representation of the object. | |
223 | |
224 @return: a dict of strings. | |
225 @rtype: C{dict} binding C{unicode} to C{unicode} | |
226 """ | |
227 result = {} | |
228 for attr in ('count', 'index', 'first', 'last'): | |
229 value = getattr(self, attr) | |
230 if value is not None: | |
231 result[attr] = unicode(value) | |
232 return result | |
233 | |
234 | |
235 class PubSubRequest(pubsub.PubSubRequest): | |
236 """PubSubRequest extension to handle RSM. | |
237 | |
238 @ivar rsm: RSM request instance. | |
239 @type rsm: L{RSMRequest} | |
240 """ | |
241 | |
242 rsm = None | |
243 | |
244 def __init__(self, verb=None): | |
245 pubsub.PubSubRequest.__init__(self, verb) | |
246 self._parameters = copy.deepcopy(pubsub.PubSubRequest._parameters) | |
247 self._parameters['items'].append('rsm') | |
248 | |
249 def _parse_rsm(self, verbElement): | |
250 self.rsm = RSMRequest.parse(verbElement.parent) | |
251 | |
252 def _render_rsm(self, verbElement): | |
253 if self.rsm: | |
254 self.rsm.render(verbElement.parent) | |
255 | |
256 | |
257 class PubSubClient(pubsub.PubSubClient): | |
258 """PubSubClient extension to handle RSM.""" | |
259 | |
260 _rsm_responses = {} | |
261 | |
262 def items(self, service, nodeIdentifier, maxItems=None, itemIdentifiers=None, | |
263 subscriptionIdentifier=None, sender=None, ext_data=None): | |
264 """ | |
265 Retrieve previously published items from a publish subscribe node. | |
266 | |
267 @param service: The publish subscribe service that keeps the node. | |
268 @type service: L{JID<twisted.words.protocols.jabber.jid.JID>} | |
269 | |
270 @param nodeIdentifier: The identifier of the node. | |
271 @type nodeIdentifier: C{unicode} | |
272 | |
273 @param maxItems: Optional limit on the number of retrieved items. | |
274 @type maxItems: C{int} | |
275 | |
276 @param itemIdentifiers: Identifiers of the items to be retrieved. | |
277 @type itemIdentifiers: C{set} | |
278 | |
279 @param subscriptionIdentifier: Optional subscription identifier. In | |
280 case the node has been subscribed to multiple times, this narrows | |
281 the results to the specific subscription. | |
282 @type subscriptionIdentifier: C{unicode} | |
283 | |
284 @param ext_data: extension data. | |
285 @type ext_data: L{dict} | |
286 | |
287 @return: a Deferred that fires a C{list} of L{domish.Element}. | |
288 @rtype: L{defer.Deferred} | |
289 """ | |
290 request = PubSubRequest('items') # that's a rsm.PubSubRequest instance | |
291 request.recipient = service | |
292 request.nodeIdentifier = nodeIdentifier | |
293 if maxItems: | |
294 request.maxItems = str(int(maxItems)) | |
295 request.subscriptionIdentifier = subscriptionIdentifier | |
296 request.sender = sender | |
297 request.itemIdentifiers = itemIdentifiers | |
298 if ext_data and 'rsm' in ext_data: | |
299 request.rsm = ext_data['rsm'] | |
300 | |
301 def cb(iq): | |
302 items = [] | |
303 if iq.pubsub.items: | |
304 for element in iq.pubsub.items.elements(): | |
305 if element.uri == pubsub.NS_PUBSUB and element.name == 'item': | |
306 items.append(element) | |
307 | |
308 if request.rsm: | |
309 response = RSMResponse.parse(iq.pubsub) | |
310 if response is not None: | |
311 self._rsm_responses[ext_data['id']] = response | |
312 return items | |
313 | |
314 d = request.send(self.xmlstream) | |
315 d.addCallback(cb) | |
316 return d | |
317 | |
318 def getRSMResponse(self, id): | |
319 """ | |
320 Post-retrieve the RSM response data after items retrieval is done. | |
321 | |
322 @param id: extension data ID | |
323 @type id: C{unicode} | |
324 | |
325 @return: dict representation of the RSM response. | |
326 @rtype: C{dict} of C{unicode} | |
327 """ | |
328 # This method exists to not modify the return value of self.items. | |
329 if id not in self._rsm_responses: | |
330 return {} | |
331 result = self._rsm_responses[id].toDict() | |
332 del self._rsm_responses[id] | |
333 return result | |
334 | |
335 | |
336 class PubSubService(pubsub.PubSubService): | |
337 """PubSubService extension to handle RSM.""" | |
338 | |
339 _request_class = PubSubRequest | |
340 | |
341 def _toResponse_items(self, result, resource, request): | |
342 response = pubsub.PubSubService._toResponse_items(self, result, | |
343 resource, request) | |
344 set_elts = [elt for elt in result if elt.name == 'set'] | |
345 if set_elts: | |
346 assert(len(set_elts) == 1) | |
347 response.addChild(set_elts[0]) | |
348 | |
349 return response |