comparison sat_tmp/wokkel/rsm.py @ 45:c8cb4e867897

made proper package + installation
author Arnaud Joset <info@agayon.be>
date Thu, 02 Nov 2017 22:50:59 +0100
parents wokkel/rsm.py@213122b92b08
children f4d569dc8e6b
comparison
equal deleted inserted replaced
44:7430d1f6db22 45:c8cb4e867897
1 # -*- coding: utf-8 -*-
2 # -*- test-case-name: wokkel.test.test_rsm -*-
3 #
4 # SàT Wokkel extension for Result Set Management (XEP-0059)
5 # Copyright (C) 2015 Adien Cossa (souliane@mailoo.org)
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 """
21 XMPP Result Set Management protocol.
22
23 This protocol is specified in
24 U{XEP-0059<http://xmpp.org/extensions/xep-0059.html>}.
25 """
26
27 from twisted.words.xish import domish
28 from twisted.words.protocols.jabber import error
29
30 import pubsub
31 import copy
32
33
34 NS_RSM = 'http://jabber.org/protocol/rsm'
35
36
37 class RSMError(error.StanzaError):
38 """
39 RSM error.
40 """
41 def __init__(self, text=None):
42 error.StanzaError.__init__(self, 'bad-request', text=text)
43
44
45 class RSMNotFoundError(Exception):
46 """
47 An expected RSM element has not been found.
48 """
49
50
51 class RSMRequest(object):
52 """
53 A Result Set Management request.
54
55 @ivar max_: limit on the number of retrieved items.
56 @itype max_: C{int} or C{unicode}
57
58 @ivar index: starting index of the requested page.
59 @itype index: C{int} or C{unicode} or C{None}
60
61 @ivar after: ID of the element immediately preceding the page.
62 @itype after: C{unicode}
63
64 @ivar before: ID of the element immediately following the page.
65 @itype before: C{unicode}
66 """
67
68 def __init__(self, max_=10, after=None, before=None, index=None):
69 self.max = int(max_)
70
71 if index is not None:
72 assert after is None and before is None
73 index = int(index)
74 self.index = index
75
76 if after is not None:
77 assert before is None
78 assert isinstance(after, basestring)
79 self.after = after
80
81 if before is not None:
82 assert isinstance(before, basestring)
83 self.before = before
84
85 def __str__(self):
86 return "RSM Request: max={0.max} after={0.after} before={0.before} index={0.index}".format(self)
87
88 @classmethod
89 def fromElement(cls, element):
90 """Parse the given request element.
91
92 @param element: request containing a set element, or set element itself.
93 @type element: L{domish.Element}
94
95 @return: RSMRequest instance.
96 @rtype: L{RSMRequest}
97 """
98
99 if element.name == 'set' and element.uri == NS_RSM:
100 set_elt = element
101 else:
102 try:
103 set_elt = element.elements(NS_RSM, 'set').next()
104 except StopIteration:
105 raise RSMNotFoundError()
106
107 try:
108 before_elt = set_elt.elements(NS_RSM, 'before').next()
109 except StopIteration:
110 before = None
111 else:
112 before = unicode(before_elt)
113
114 try:
115 after_elt = set_elt.elements(NS_RSM, 'after').next()
116 except StopIteration:
117 after = None
118 else:
119 after = unicode(after_elt)
120 if not after:
121 raise RSMError("<after/> element can't be empty in RSM request")
122
123 try:
124 max_elt = set_elt.elements(NS_RSM, 'max').next()
125 except StopIteration:
126 # FIXME: even if it doesn't make a lot of sense without it
127 # <max/> element is not mandatory in XEP-0059
128 raise RSMError("RSM request is missing its 'max' element")
129 else:
130 try:
131 max_ = int(unicode(max_elt))
132 except ValueError:
133 raise RSMError("bad value for 'max' element")
134
135 try:
136 index_elt = set_elt.elements(NS_RSM, 'index').next()
137 except StopIteration:
138 index = None
139 else:
140 try:
141 index = int(unicode(index_elt))
142 except ValueError:
143 raise RSMError("bad value for 'index' element")
144
145 return RSMRequest(max_, after, before, index)
146
147 def toElement(self):
148 """
149 Return the DOM representation of this RSM request.
150
151 @rtype: L{domish.Element}
152 """
153 set_elt = domish.Element((NS_RSM, 'set'))
154 set_elt.addElement('max', content=unicode(self.max))
155
156 if self.index is not None:
157 set_elt.addElement('index', content=unicode(self.index))
158
159 if self.before is not None:
160 if self.before == '': # request the last page
161 set_elt.addElement('before')
162 else:
163 set_elt.addElement('before', content=self.before)
164
165 if self.after is not None:
166 set_elt.addElement('after', content=self.after)
167
168 return set_elt
169
170 def render(self, element):
171 """Embed the DOM representation of this RSM request in the given element.
172
173 @param element: Element to contain the RSM request.
174 @type element: L{domish.Element}
175
176 @return: RSM request element.
177 @rtype: L{domish.Element}
178 """
179 set_elt = self.toElement()
180 element.addChild(set_elt)
181
182 return set_elt
183
184
185 class RSMResponse(object):
186 """
187 A Result Set Management response.
188
189 @ivar first: ID of the first element of the returned page.
190 @itype first: C{unicode}
191
192 @ivar last: ID of the last element of the returned page.
193 @itype last: C{unicode}
194
195 @ivar index: starting index of the returned page.
196 @itype index: C{int}
197
198 @ivar count: total number of items.
199 @itype count: C{int}
200
201 """
202
203 def __init__(self, first=None, last=None, index=None, count=None):
204 if first is None:
205 assert last is None and index is None
206 if last is None:
207 assert first is None
208 self.first = first
209 self.last = last
210 if count is not None:
211 self.count = int(count)
212 else:
213 self.count = None
214 if index is not None:
215 self.index = int(index)
216 else:
217 self.index = None
218
219 def __str__(self):
220 return "RSM Request: first={0.first} last={0.last} index={0.index} count={0.count}".format(self)
221
222 @classmethod
223 def fromElement(cls, element):
224 """Parse the given response element.
225
226 @param element: response element.
227 @type element: L{domish.Element}
228
229 @return: RSMResponse instance.
230 @rtype: L{RSMResponse}
231 """
232 try:
233 set_elt = element.elements(NS_RSM, 'set').next()
234 except StopIteration:
235 raise RSMNotFoundError()
236
237 try:
238 first_elt = set_elt.elements(NS_RSM, 'first').next()
239 except StopIteration:
240 first = None
241 index = None
242 else:
243 first = unicode(first_elt)
244 try:
245 index = int(first_elt['index'])
246 except KeyError:
247 index = None
248 except ValueError:
249 raise RSMError("bad index in RSM response")
250
251 try:
252 last_elt = set_elt.elements(NS_RSM, 'last').next()
253 except StopIteration:
254 if first is not None:
255 raise RSMError("RSM response is missing its 'last' element")
256 else:
257 last = None
258 else:
259 if first is None:
260 raise RSMError("RSM response is missing its 'first' element")
261 last = unicode(last_elt)
262
263 try:
264 count_elt = set_elt.elements(NS_RSM, 'count').next()
265 except StopIteration:
266 count = None
267 else:
268 try:
269 count = int(unicode(count_elt))
270 except ValueError:
271 raise RSMError("invalid count in RSM response")
272
273 return RSMResponse(first, last, index, count)
274
275 def toElement(self):
276 """
277 Return the DOM representation of this RSM request.
278
279 @rtype: L{domish.Element}
280 """
281 set_elt = domish.Element((NS_RSM, 'set'))
282 if self.first is not None:
283 first_elt = set_elt.addElement('first', content=self.first)
284 if self.index is not None:
285 first_elt['index'] = unicode(self.index)
286
287 set_elt.addElement('last', content=self.last)
288
289 if self.count is not None:
290 set_elt.addElement('count', content=unicode(self.count))
291
292 return set_elt
293
294 def render(self, element):
295 """Embed the DOM representation of this RSM response in the given element.
296
297 @param element: Element to contain the RSM response.
298 @type element: L{domish.Element}
299
300 @return: RSM request element.
301 @rtype: L{domish.Element}
302 """
303 set_elt = self.toElement()
304 element.addChild(set_elt)
305 return set_elt
306
307 def toDict(self):
308 """Return a dict representation of the object.
309
310 @return: a dict of strings.
311 @rtype: C{dict} binding C{unicode} to C{unicode}
312 """
313 result = {}
314 for attr in ('first', 'last', 'index', 'count'):
315 value = getattr(self, attr)
316 if value is not None:
317 result[attr] = unicode(value)
318 return result
319
320
321 class PubSubRequest(pubsub.PubSubRequest):
322 """PubSubRequest extension to handle RSM.
323
324 @ivar rsm: RSM request instance.
325 @type rsm: L{RSMRequest}
326 """
327
328 rsm = None
329 _parameters = copy.deepcopy(pubsub.PubSubRequest._parameters)
330 _parameters['items'].append('rsm')
331
332 def _parse_rsm(self, verbElement):
333 try:
334 self.rsm = RSMRequest.fromElement(verbElement.parent)
335 except RSMNotFoundError:
336 self.rsm = None
337
338 def _render_rsm(self, verbElement):
339 if self.rsm:
340 self.rsm.render(verbElement.parent)
341
342
343 class PubSubClient(pubsub.PubSubClient):
344 """PubSubClient extension to handle RSM."""
345
346 _request_class = PubSubRequest
347
348 def items(self, service, nodeIdentifier, maxItems=None, itemIdentifiers=None,
349 subscriptionIdentifier=None, sender=None, rsm_request=None):
350 """
351 Retrieve previously published items from a publish subscribe node.
352
353 @param service: The publish subscribe service that keeps the node.
354 @type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
355
356 @param nodeIdentifier: The identifier of the node.
357 @type nodeIdentifier: C{unicode}
358
359 @param maxItems: Optional limit on the number of retrieved items.
360 @type maxItems: C{int}
361
362 @param itemIdentifiers: Identifiers of the items to be retrieved.
363 @type itemIdentifiers: C{set}
364
365 @param subscriptionIdentifier: Optional subscription identifier. In
366 case the node has been subscribed to multiple times, this narrows
367 the results to the specific subscription.
368 @type subscriptionIdentifier: C{unicode}
369
370 @param ext_data: extension data.
371 @type ext_data: L{dict}
372
373 @return: a Deferred that fires a C{list} of C{tuple} of L{domish.Element}, L{RSMResponse}.
374 @rtype: L{defer.Deferred}
375 """
376 # XXX: we have to copy initial method instead of calling it,
377 # as original cb remove all non item elements
378 request = self._request_class('items')
379 request.recipient = service
380 request.nodeIdentifier = nodeIdentifier
381 if maxItems:
382 request.maxItems = str(int(maxItems))
383 request.subscriptionIdentifier = subscriptionIdentifier
384 request.sender = sender
385 request.itemIdentifiers = itemIdentifiers
386 request.rsm = rsm_request
387
388 def cb(iq):
389 items = []
390 pubsub_elt = iq.pubsub
391 if pubsub_elt.items:
392 for element in pubsub_elt.items.elements(pubsub.NS_PUBSUB, 'item'):
393 items.append(element)
394
395 try:
396 rsm_response = RSMResponse.fromElement(pubsub_elt)
397 except RSMNotFoundError:
398 rsm_response = None
399 return (items, rsm_response)
400
401 d = request.send(self.xmlstream)
402 d.addCallback(cb)
403 return d
404
405
406 class PubSubService(pubsub.PubSubService):
407 """PubSubService extension to handle RSM."""
408
409 _request_class = PubSubRequest
410
411 def _toResponse_items(self, elts, resource, request):
412 # default method only manage <item/> elements
413 # but we need to add RSM set element
414 rsm_elt = None
415 for idx, elt in enumerate(reversed(elts)):
416 if elt.name == "set" and elt.uri == NS_RSM:
417 rsm_elt = elts.pop(-1-idx)
418 break
419
420 response = pubsub.PubSubService._toResponse_items(self, elts,
421 resource, request)
422 if rsm_elt is not None:
423 response.addChild(rsm_elt)
424
425 return response