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