Mercurial > libervia-backend
comparison src/plugins/plugin_xep_0277.py @ 1459:4c4f88d7b156
plugins xep-0060, xep-0163, xep-0277, groupblog: bloging improvments (huge patch, sorry):
/!\ not everything is working yet, and specially groupblogs are broken /!\
- renamed bridge api to use prefixed methods (e.g. psSubscribeToMany instead of subscribeToMany in PubSub)
- (xep-0060): try to find a default PubSub service, and put it in client.pubsub_service
- (xep-0060): extra dictionary can be used in bridge method for RSM and other options
- (xep-0060): XEP_0060.addManagedNode and XEP_0060.removeManagedNode allow to easily catch notifications for a specific node
- (xep-0060): retractItem manage "notify" attribute
- (xep-0060): new signal psEvent will be used to transmit notifications to frontends
- (xep-0060, constants): added a bunch of useful constants
- (xep-0163): removed personalEvent in favor of psEvent
- (xep-0163): addPEPEvent now filter non PEP events for in_callback
- (xep-0277): use of new XEP-0060 plugin's addManagedNode
- (xep-0277): fixed author handling for incoming blogs: author is the human readable name, author_jid it jid, and author_jid_verified is set to True is the jid is checked
- (xep-0277): reworked data2entry with Twisted instead of feed, item_id can now be specified, <content/> is changed to <title/> if there is only content
- (xep-0277): comments are now managed here (core removed from groupblog)
- (xep-0277): (comments) node is created if needed, default pubsub service is used if available, else PEP
- (xep-0277): retract is managed
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 16 Aug 2015 00:39:44 +0200 |
parents | 4e2fab4de195 |
children | 83f71763e1a7 |
comparison
equal
deleted
inserted
replaced
1458:832846fefe85 | 1459:4c4f88d7b156 |
---|---|
19 | 19 |
20 from sat.core.i18n import _ | 20 from sat.core.i18n import _ |
21 from sat.core.constants import Const as C | 21 from sat.core.constants import Const as C |
22 from sat.core.log import getLogger | 22 from sat.core.log import getLogger |
23 log = getLogger(__name__) | 23 log = getLogger(__name__) |
24 from twisted.words.protocols.jabber import jid | 24 from twisted.words.protocols.jabber import jid, error |
25 from twisted.words.xish import domish | |
25 from twisted.internet import defer | 26 from twisted.internet import defer |
26 from twisted.python import failure | 27 from twisted.python import failure |
27 from sat.core import exceptions | 28 from sat.core import exceptions |
28 from sat.tools.xml_tools import ElementParser | 29 from sat.tools import xml_tools |
29 from sat.tools import sat_defer | 30 from sat.tools import sat_defer |
30 | 31 |
32 # XXX: tmp.pubsub is actually use instead of wokkel version | |
31 from wokkel import pubsub | 33 from wokkel import pubsub |
32 from wokkel import rsm | 34 from feed.date import rfc3339 |
33 from feed import atom, date | |
34 import uuid | 35 import uuid |
35 from time import time | 36 import time |
36 import urlparse | 37 import urlparse |
37 from cgi import escape | 38 import urllib |
38 | 39 |
39 NS_MICROBLOG = 'urn:xmpp:microblog:0' | 40 NS_MICROBLOG = 'urn:xmpp:microblog:0' |
40 NS_ATOM = 'http://www.w3.org/2005/Atom' | 41 NS_ATOM = 'http://www.w3.org/2005/Atom' |
41 NS_XHTML = 'http://www.w3.org/1999/xhtml' | 42 NS_XHTML = 'http://www.w3.org/1999/xhtml' |
42 NS_PUBSUB_EVENT = "{}{}".format(pubsub.NS_PUBSUB, "#event") | 43 NS_PUBSUB_EVENT = "{}{}".format(pubsub.NS_PUBSUB, "#event") |
44 NS_COMMENT_PREFIX = '{}:comments/'.format(NS_MICROBLOG) | |
45 | |
43 | 46 |
44 PLUGIN_INFO = { | 47 PLUGIN_INFO = { |
45 "name": "Microblogging over XMPP Plugin", | 48 "name": "Microblogging over XMPP Plugin", |
46 "import_name": "XEP-0277", | 49 "import_name": "XEP-0277", |
47 "type": "XEP", | 50 "type": "XEP", |
63 def __init__(self, host): | 66 def __init__(self, host): |
64 log.info(_("Microblogging plugin initialization")) | 67 log.info(_("Microblogging plugin initialization")) |
65 self.host = host | 68 self.host = host |
66 self._p = self.host.plugins["XEP-0060"] # this facilitate the access to pubsub plugin | 69 self._p = self.host.plugins["XEP-0060"] # this facilitate the access to pubsub plugin |
67 self.rt_sessions = sat_defer.RTDeferredSessions() | 70 self.rt_sessions = sat_defer.RTDeferredSessions() |
68 self.host.plugins["XEP-0163"].addPEPEvent("MICROBLOG", NS_MICROBLOG, self.microblogCB, self.sendMicroblog, notify=False) | 71 self.host.plugins["XEP-0060"].addManagedNode(None, items_cb=self._itemsReceived) |
69 host.bridge.addMethod("getLastMicroblogs", ".plugin", | 72 |
70 in_sign='sis', out_sign='(aa{ss}a{ss})', | 73 host.bridge.addMethod("mbSend", ".plugin", |
71 method=self._getLastMicroblogs, | 74 in_sign='ssa{ss}s', out_sign='', |
72 async=True, | 75 method=self._mbSend, |
73 doc={'summary': 'retrieve items', | |
74 'param_0': 'jid: publisher of wanted microblog', | |
75 'param_1': 'max_items: see XEP-0060 #6.5.7', | |
76 'param_2': '%(doc_profile)s', | |
77 'return': 'list of microblog data (dict)'}) | |
78 host.bridge.addMethod("setMicroblogAccess", ".plugin", in_sign='ss', out_sign='', | |
79 method=self.setMicroblogAccess, | |
80 async=True) | 76 async=True) |
81 host.bridge.addMethod("mBSubscribeToMany", ".plugin", in_sign='sass', out_sign='s', | 77 host.bridge.addMethod("mbRetract", ".plugin", |
82 method=self._mBSubscribeToMany) | 78 in_sign='ssss', out_sign='', |
83 host.bridge.addMethod("mBGetFromManyRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssaa{ss}a{ss}))', | 79 method=self._mbRetract, |
84 method=self._mBGetFromManyRTResult, async=True) | 80 async=True) |
85 host.bridge.addMethod("mBGetFromMany", ".plugin", in_sign='sasia{ss}s', out_sign='s', | 81 host.bridge.addMethod("mbGetLast", ".plugin", |
86 method=self._mBGetFromMany) | 82 in_sign='ssia{ss}s', out_sign='(aa{ss}a{ss})', |
87 host.bridge.addMethod("mBGetFromManyWithCommentsRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssa(a{ss}a(sssaa{ss}a{ss}))a{ss}))', | 83 method=self._mbGetLast, |
88 method=self._mBGetFromManyWithCommentsRTResult, async=True) | 84 async=True) |
89 host.bridge.addMethod("mBGetFromManyWithComments", ".plugin", in_sign='sasiia{ss}a{ss}s', out_sign='s', method=self._mBGetFromManyWithComments) | 85 host.bridge.addMethod("mbSetAccess", ".plugin", in_sign='ss', out_sign='', |
86 method=self.mbSetAccess, | |
87 async=True) | |
88 host.bridge.addMethod("mbSetAccess", ".plugin", in_sign='ss', out_sign='', | |
89 method=self.mbSetAccess, | |
90 async=True) | |
91 host.bridge.addMethod("mbSubscribeToMany", ".plugin", in_sign='sass', out_sign='s', | |
92 method=self._mbSubscribeToMany) | |
93 host.bridge.addMethod("mbGetFromManyRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssaa{ss}a{ss}))', | |
94 method=self._mbGetFromManyRTResult, async=True) | |
95 host.bridge.addMethod("mbGetFromMany", ".plugin", in_sign='sasia{ss}s', out_sign='s', | |
96 method=self._mbGetFromMany) | |
97 host.bridge.addMethod("mbGetFromManyWithCommentsRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssa(a{ss}a(sssaa{ss}a{ss}))a{ss}))', | |
98 method=self._mbGetFromManyWithCommentsRTResult, async=True) | |
99 host.bridge.addMethod("mbGetFromManyWithComments", ".plugin", in_sign='sasiia{ss}a{ss}s', out_sign='s', method=self._mbGetFromManyWithComments) | |
90 | 100 |
91 ## plugin management methods ## | 101 ## plugin management methods ## |
92 | 102 |
93 def microblogCB(self, itemsEvent, profile): | 103 def _itemsReceived(self, itemsEvent, profile): |
94 """Callback to "MICROBLOG" PEP event.""" | 104 """Callback which manage items notifications (publish + retract)""" |
95 def manageItem(microblog_data): | 105 if not itemsEvent.nodeIdentifier.startswith(NS_MICROBLOG): |
96 self.host.bridge.personalEvent(itemsEvent.sender.full(), "MICROBLOG", microblog_data, profile) | 106 return |
107 def manageItem(data, event): | |
108 self.host.bridge.psEvent(C.PS_MICROBLOG, itemsEvent.sender.full(), itemsEvent.nodeIdentifier, event, data, profile) | |
97 | 109 |
98 for item in itemsEvent.items: | 110 for item in itemsEvent.items: |
99 self.item2mbdata(item).addCallbacks(manageItem, lambda failure: None) | 111 if item.name == C.PS_ITEM: |
112 self.item2mbdata(item).addCallbacks(manageItem, lambda failure: None, (C.PS_PUBLISH,)) | |
113 elif item.name == C.PS_RETRACT: | |
114 manageItem({'id': item['id']}, C.PS_RETRACT) | |
115 else: | |
116 raise exceptions.InternalError("Invalid event value") | |
117 | |
100 | 118 |
101 ## data/item transformation ## | 119 ## data/item transformation ## |
102 | |
103 def _removeXHTMLMarkups(self, xhtml): | |
104 """Remove XHTML markups from the given string. | |
105 | |
106 @param xhtml: the XHTML string to be cleaned | |
107 @return: a Deferred instance for the cleaned string | |
108 """ | |
109 return self.host.plugins["TEXT-SYNTAXES"].convert(xhtml, | |
110 self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML, | |
111 self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT, | |
112 False) | |
113 | 120 |
114 @defer.inlineCallbacks | 121 @defer.inlineCallbacks |
115 def item2mbdata(self, item_elt): | 122 def item2mbdata(self, item_elt): |
116 """Convert an XML Item to microblog data used in bridge API | 123 """Convert an XML Item to microblog data used in bridge API |
117 | 124 |
149 if type_ == 'xhtml': | 156 if type_ == 'xhtml': |
150 data_elt = elem.firstChildElement() | 157 data_elt = elem.firstChildElement() |
151 if data_elt.uri != NS_XHTML: | 158 if data_elt.uri != NS_XHTML: |
152 raise failure.Failure(exceptions.DataError(_('Content of type XHTML must declare its namespace!'))) | 159 raise failure.Failure(exceptions.DataError(_('Content of type XHTML must declare its namespace!'))) |
153 key = check_conflict(u'{}_xhtml'.format(elem.name)) | 160 key = check_conflict(u'{}_xhtml'.format(elem.name)) |
154 data = unicode(data_elt) | 161 data = data_elt.toXml() |
155 microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].clean_xhtml(data) | 162 microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].clean_xhtml(data) |
156 else: | 163 else: |
157 key = check_conflict(elem.name) | 164 key = check_conflict(elem.name) |
158 microblog_data[key] = unicode(elem) | 165 microblog_data[key] = unicode(elem) |
159 | 166 |
194 yield parseElement(content_elt) | 201 yield parseElement(content_elt) |
195 | 202 |
196 # we check that text content is present | 203 # we check that text content is present |
197 for key in ('title', 'content'): | 204 for key in ('title', 'content'): |
198 if key not in microblog_data and ('{}_xhtml'.format(key)) in microblog_data: | 205 if key not in microblog_data and ('{}_xhtml'.format(key)) in microblog_data: |
199 log.warning(u"item {id_} provide a {key}_xhtml data but not a text one".format(id_, key)) | 206 log.warning(u"item {id_} provide a {key}_xhtml data but not a text one".format(id_=id_, key=key)) |
200 # ... and do the conversion if it's not | 207 # ... and do the conversion if it's not |
201 microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].\ | 208 microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].\ |
202 convert(microblog_data['{}_xhtml'.format(key)], | 209 convert(microblog_data['{}_xhtml'.format(key)], |
203 self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML, | 210 self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML, |
204 self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT, | 211 self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT, |
216 try: | 223 try: |
217 updated_elt = entry_elt.elements(NS_ATOM, 'updated').next() | 224 updated_elt = entry_elt.elements(NS_ATOM, 'updated').next() |
218 except StopIteration: | 225 except StopIteration: |
219 msg = u'No atom updated element found in the pubsub item {}'.format(id_) | 226 msg = u'No atom updated element found in the pubsub item {}'.format(id_) |
220 raise failure.Failure(exceptions.DataError(msg)) | 227 raise failure.Failure(exceptions.DataError(msg)) |
221 microblog_data['updated'] = unicode(date.rfc3339.tf_from_timestamp(unicode(updated_elt))) | 228 microblog_data['updated'] = unicode(rfc3339.tf_from_timestamp(unicode(updated_elt))) |
222 try: | 229 try: |
223 published_elt = entry_elt.elements(NS_ATOM, 'published').next() | 230 published_elt = entry_elt.elements(NS_ATOM, 'published').next() |
224 except StopIteration: | 231 except StopIteration: |
225 microblog_data['published'] = microblog_data['updated'] | 232 microblog_data['published'] = microblog_data['updated'] |
226 else: | 233 else: |
227 microblog_data['published'] = unicode(date.rfc3339.tf_from_timestamp(unicode(published_elt))) | 234 microblog_data['published'] = unicode(rfc3339.tf_from_timestamp(unicode(published_elt))) |
228 | 235 |
229 # links | 236 # links |
230 for link_elt in entry_elt.elements(NS_ATOM, 'link'): | 237 for link_elt in entry_elt.elements(NS_ATOM, 'link'): |
231 if link_elt.getAttribute('rel') == 'replies' and link_elt.getAttribute('title') == 'comments': | 238 if link_elt.getAttribute('rel') == 'replies' and link_elt.getAttribute('title') == 'comments': |
232 key = check_conflict('comments', True) | 239 key = check_conflict('comments', True) |
244 title = link_elt.getAttribute('title','') | 251 title = link_elt.getAttribute('title','') |
245 href = link_elt.getAttribute('href','') | 252 href = link_elt.getAttribute('href','') |
246 log.warning(u"Unmanaged link element: rel={rel} title={title} href={href}".format(rel=rel, title=title, href=href)) | 253 log.warning(u"Unmanaged link element: rel={rel} title={title} href={href}".format(rel=rel, title=title, href=href)) |
247 | 254 |
248 # author | 255 # author |
256 publisher = item_elt.getAttribute("publisher") | |
249 try: | 257 try: |
250 author_elt = entry_elt.elements(NS_ATOM, 'author').next() | 258 author_elt = entry_elt.elements(NS_ATOM, 'author').next() |
251 except StopIteration: | 259 except StopIteration: |
252 log.debug("Can't find author element in item {}".format(id_)) | 260 log.debug("Can't find author element in item {}".format(id_)) |
253 else: | 261 else: |
261 # uri | 269 # uri |
262 try: | 270 try: |
263 uri_elt = author_elt.elements(NS_ATOM, 'uri').next() | 271 uri_elt = author_elt.elements(NS_ATOM, 'uri').next() |
264 except StopIteration: | 272 except StopIteration: |
265 log.debug("No uri element found in author element of item {}".format(id_)) | 273 log.debug("No uri element found in author element of item {}".format(id_)) |
274 if publisher: | |
275 microblog_data['author_jid'] = publisher | |
266 else: | 276 else: |
267 uri = unicode(uri_elt) | 277 uri = unicode(uri_elt) |
268 if uri.startswith("xmpp:"): | 278 if uri.startswith("xmpp:"): |
269 uri = uri[5:] | 279 uri = uri[5:] |
270 microblog_data['author_uri'] = uri | 280 microblog_data['author_jid'] = uri |
271 if item_elt.getAttribute("publisher") == uri: | 281 else: |
272 microblog_data['author_uri_verified'] = C.BOOL_TRUE | 282 microblog_data['author_jid'] = item_elt.getAttribute("publisher") or "" |
283 | |
284 if not publisher: | |
285 log.debug("No publisher attribute, we can't verify author jid") | |
286 microblog_data['author_jid_verified'] = C.BOOL_FALSE | |
287 elif publisher == uri: | |
288 microblog_data['author_jid_verified'] = C.BOOL_TRUE | |
273 else: | 289 else: |
274 log.warning("item atom:uri differ from publisher attribute, spoofing attempt ? atom:uri = {} publisher = {}".format(uri, item_elt.getAttribute("publisher"))) | 290 log.warning("item atom:uri differ from publisher attribute, spoofing attempt ? atom:uri = {} publisher = {}".format(uri, item_elt.getAttribute("publisher"))) |
275 microblog_data['author_uri_verified'] = C.BOOL_FALSE | 291 microblog_data['author_jid_verified'] = C.BOOL_FALSE |
276 # email | 292 # email |
277 try: | 293 try: |
278 email_elt = author_elt.elements(NS_ATOM, 'email').next() | 294 email_elt = author_elt.elements(NS_ATOM, 'email').next() |
279 except StopIteration: | 295 except StopIteration: |
280 pass | 296 pass |
282 microblog_data['author_email'] = unicode(email_elt) | 298 microblog_data['author_email'] = unicode(email_elt) |
283 | 299 |
284 defer.returnValue(microblog_data) | 300 defer.returnValue(microblog_data) |
285 | 301 |
286 @defer.inlineCallbacks | 302 @defer.inlineCallbacks |
287 def data2entry(self, data, profile): | 303 def data2entry(self, data, item_id=None, profile_key=C.PROF_KEY_NONE): |
288 """Convert a data dict to en entry usable to create an item | 304 """Convert a data dict to en entry usable to create an item |
289 | 305 |
290 @param data: data dict as given by bridge method. | 306 @param data: data dict as given by bridge method. |
307 @param item_id(unicode, None): id of the item to use | |
308 if None the id will be generated randomly | |
291 @return: deferred which fire domish.Element | 309 @return: deferred which fire domish.Element |
292 """ | 310 """ |
293 #TODO: rewrite this directly with twisted (i.e. without atom / reparsing) | 311 if item_id is None: |
294 _uuid = unicode(uuid.uuid1()) | 312 item_id = unicode(uuid.uuid4()) |
295 _entry = atom.Entry() | 313 client = self.host.getClient(profile_key) |
296 _entry.title = '' # reset the default value which is not empty | 314 entry_elt = domish.Element((NS_ATOM, 'entry')) |
297 | 315 |
298 elems = {'title': atom.Title, 'content': atom.Content} | 316 ## content and title ## |
299 synt = self.host.plugins["TEXT-SYNTAXES"] | 317 synt = self.host.plugins["TEXT-SYNTAXES"] |
300 | 318 |
301 # loop on ('title', 'title_rich', 'title_xhtml', 'content', 'content_rich', 'content_xhtml') | 319 for elem_name in ('title', 'content'): |
302 for key in elems.keys(): | 320 for type_ in ['', '_rich', '_xhtml']: |
303 for type_ in ['', 'rich', 'xhtml']: | 321 attr = "{}{}".format(elem_name, type_) |
304 attr = "%s_%s" % (key, type_) if type_ else key | |
305 if attr in data: | 322 if attr in data: |
323 elem = entry_elt.addElement(elem_name) | |
306 if type_: | 324 if type_: |
307 if type_ == 'rich': # convert input from current syntax to XHTML | 325 if type_ == '_rich': # convert input from current syntax to XHTML |
308 converted = yield synt.convert(data[attr], synt.getCurrentSyntax(profile), "XHTML") | 326 converted = yield synt.convert(data[attr], synt.getCurrentSyntax(profile_key), "XHTML") |
327 if '{}_xhtml'.format(elem_name) in data: | |
328 raise failure.Failure(exceptions.DataError(_("Can't have xhtml and rich content at the same time"))) | |
309 else: # clean the XHTML input | 329 else: # clean the XHTML input |
310 converted = yield synt.clean_xhtml(data[attr]) | 330 converted = yield synt.clean_xhtml(data[attr]) |
311 elem = elems[key]((u'<div xmlns="%s">%s</div>' % (NS_XHTML, converted)).encode('utf-8')) | 331 |
312 elem.attrs['type'] = 'xhtml' | 332 xml_content = u'<div xmlns="{ns}">{converted}</div>'.format( |
313 if hasattr(_entry, '%s_xhtml' % key): | 333 ns=NS_XHTML, |
314 raise failure.Failure(exceptions.DataError(_("Can't have xhtml and rich content at the same time"))) | 334 converted=converted) |
315 setattr(_entry, '%s_xhtml' % key, elem) | 335 elem.addChild(xml_tools.ElementParser()(xml_content)) |
336 elem['type'] = 'xhtml' | |
337 if elem_name not in data: | |
338 # there is raw text content, which is mandatory | |
339 # so we create one from xhtml content | |
340 elem_txt = entry_elt.addElement(elem_name) | |
341 text_content = yield self.host.plugins["TEXT-SYNTAXES"].convert(xml_content, | |
342 self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML, | |
343 self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT, | |
344 False) | |
345 elem_txt.addContent(text_content) | |
346 elem_txt['type'] = 'text' | |
347 | |
316 else: # raw text only needs to be escaped to get HTML-safe sequence | 348 else: # raw text only needs to be escaped to get HTML-safe sequence |
317 elem = elems[key](escape(data[attr]).encode('utf-8')) | 349 elem.addContent(data[attr]) |
318 elem.attrs['type'] = 'text' | 350 elem['type'] = 'text' |
319 setattr(_entry, key, elem) | 351 |
320 if not getattr(_entry, key).text: | 352 try: |
321 if hasattr(_entry, '%s_xhtml' % key): | 353 entry_elt.elements(NS_ATOM, 'title').next() |
322 text = yield self._removeXHTMLMarkups(getattr(_entry, '%s_xhtml' % key).text) | 354 except StopIteration: |
323 setattr(_entry, key, text) | 355 # we have no title element which is mandatory |
324 if not _entry.title.text: # eventually move the data from content to title | 356 # so we transform content element to title |
325 _entry.title = _entry.content.text | 357 elems = list(entry_elt.elements(NS_ATOM, 'content')) |
326 _entry.title.attrs['type'] = _entry.content.attrs['type'] | 358 if not elems: |
327 _entry.content.text = '' | 359 raise exceptions.DataError("There must be at least one content or title element") |
328 _entry.content.attrs['type'] = '' | 360 for elem in elems: |
329 if hasattr(_entry, 'content_xhtml'): | 361 elem.name = 'title' |
330 _entry.title_xhtml = atom.Title(_entry.content_xhtml.text) | 362 |
331 _entry.title_xhtml.attrs['type'] = _entry.content_xhtml.attrs['type'] | 363 ## author ## |
332 _entry.content_xhtml.text = '' | 364 author_elt = entry_elt.addElement('author') |
333 _entry.content_xhtml.attrs['type'] = '' | 365 try: |
334 | 366 author_name = data['author'] |
335 _entry.author = atom.Author() | 367 except KeyError: |
336 _entry.author.name = data.get('author', self.host.getJidNStream(profile)[0].userhost()).encode('utf-8') | 368 # FIXME: must use better name |
337 _entry.updated = float(data.get('updated', time())) | 369 author_name = client.jid.user |
338 _entry.published = float(data.get('published', time())) | 370 author_elt.addElement('name', content=author_name) |
339 entry_id = data.get('id', unicode(_uuid)) | 371 |
340 _entry.id = entry_id.encode('utf-8') | 372 try: |
373 author_jid_s = data['author_jid'] | |
374 except KeyError: | |
375 author_jid_s = client.jid.userhost() | |
376 author_elt.addElement('uri', content="xmpp:{}".format(author_jid_s)) | |
377 | |
378 ## published/updated time ## | |
379 current_time = time.time() | |
380 entry_elt.addElement('updated', | |
381 content=rfc3339.timestamp_from_tf(float(data.get('updated', current_time)))) | |
382 entry_elt.addElement('published', | |
383 content=rfc3339.timestamp_from_tf(float(data.get('published', current_time)))) | |
384 | |
385 ## id ## | |
386 entry_id = data.get('id', item_id) # FIXME: use a proper id (see XEP-0277 §7.1) | |
387 entry_elt.addElement('id', content=entry_id) # | |
388 | |
389 ## comments ## | |
341 if 'comments' in data: | 390 if 'comments' in data: |
342 link = atom.Link() | 391 link_elt = entry_elt.addElement('link') |
343 link.attrs['href'] = data['comments'] | 392 link_elt['href'] = data['comments'] |
344 link.attrs['rel'] = 'replies' | 393 link_elt['rel'] = 'replies' |
345 link.attrs['title'] = 'comments' | 394 link_elt['title'] = 'comments' |
346 _entry.links.append(link) | 395 |
347 _entry_elt = ElementParser()(str(_entry).decode('utf-8')) | 396 ## final item building ## |
348 item = pubsub.Item(id=entry_id, payload=_entry_elt) | 397 item_elt = pubsub.Item(id=item_id, payload=entry_elt) |
349 defer.returnValue(item) | 398 defer.returnValue(item_elt) |
350 | 399 |
351 ## publish ## | 400 ## publish ## |
352 | 401 |
353 @defer.inlineCallbacks | 402 @defer.inlineCallbacks |
354 def sendMicroblog(self, data, profile): | 403 def _manageComments(self, access, mb_data, service, node, item_id, profile): |
404 """Check comments keys in mb_data and create comments node if necessary | |
405 | |
406 @param access(unicode): access model | |
407 @param mb_data(dict): microblog mb_data | |
408 @param service(jid.JID): Pubsub service of the parent item | |
409 @param node(unicode): node of the parent item | |
410 @param item_id(unicoe): id of the parent item | |
411 """ | |
412 allow_comments = C.bool(mb_data.pop("allow_comments", "false")) | |
413 if not allow_comments: | |
414 return | |
415 | |
416 client = self.host.getClient(profile) | |
417 | |
418 options = {self._p.OPT_ACCESS_MODEL: access, | |
419 self._p.OPT_PERSIST_ITEMS: 1, | |
420 self._p.OPT_MAX_ITEMS: -1, | |
421 self._p.OPT_DELIVER_PAYLOADS: 1, | |
422 self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, | |
423 self._p.OPT_PUBLISH_MODEL: "subscribers", # TODO: should be open if *both* node and item access_model are open (public node and item) | |
424 } | |
425 | |
426 comments_node_base = u"{}{}".format(NS_COMMENT_PREFIX, item_id) | |
427 comments_node = comments_node_base | |
428 | |
429 suffix = None | |
430 comments_service = client.pubsub_service if client.pubsub_service is not None else service | |
431 max_tries = 3 | |
432 | |
433 for i in xrange(max_tries+1): | |
434 try: | |
435 yield self._p.createNode(comments_service, comments_node, options, profile_key=profile) | |
436 break | |
437 except error.StanzaError as e: | |
438 if e.condition == 'conflict' and i<max_tries: | |
439 log.warning(u"node {} already exists on service {}".format(comments_node, comments_service)) | |
440 suffix = 0 if suffix is None else suffix + 1 | |
441 comments_node = u"{}_{}".format(comments_node_base, suffix) | |
442 else: | |
443 raise e | |
444 | |
445 if comments_service is None: | |
446 comments_service = client.jid.userhostJID() | |
447 | |
448 mb_data['comments'] = "xmpp:%(service)s?%(query)s" % { | |
449 'service': comments_service.userhost(), | |
450 'query': urllib.urlencode([('node', comments_node.encode('utf-8'))]) | |
451 } | |
452 | |
453 def _mbSend(self, service, node, data, profile_key): | |
454 service = jid.JID(service) if service else None | |
455 node = node if node else NS_MICROBLOG | |
456 profile = self.host.memory.getProfileName(profile_key) | |
457 return self.send(service, node, data, profile) | |
458 | |
459 @defer.inlineCallbacks | |
460 def send(self, service=None, node=NS_MICROBLOG, data=None, profile=None): | |
355 """Send XEP-0277's microblog data | 461 """Send XEP-0277's microblog data |
356 | 462 |
357 @param data: must include content | 463 @param service(jid.JID, None): PubSub service where the microblog must be published |
358 @param profile: profile which send the mood""" | 464 None to publish on profile's PEP |
359 if 'content' not in data: | 465 @param node(unicode): PubSub node to use (defaut to microblog NS) |
360 log.error("Microblog data must contain at least 'content' key") | 466 @param data(dict): microblog data (must include at least a "content" or a "title" key). |
361 raise failure.Failure(exceptions.DataError('no "content" key found')) | 467 see http://wiki.goffi.org/wiki/Bridge_API_-_Microblogging/en for details |
362 content = data['content'] | 468 @param profile: %(doc_profile)s |
363 if not content: | 469 """ |
364 log.error("Microblog data's content value must not be empty") | 470 assert profile is not None |
365 raise failure.Failure(exceptions.DataError('empty content')) | 471 |
366 item = yield self.data2entry(data, profile) | 472 item_id = data.get('id') or unicode(uuid.uuid4()) |
367 ret = yield self._p.publish(None, NS_MICROBLOG, [item], profile_key=profile) | 473 try: |
474 yield self._manageComments(self._p.ACCESS_OPEN, data, service, node, item_id, profile) | |
475 except error.StanzaError: | |
476 log.warning("Can't create comments node for item {}".format(item_id)) | |
477 item = yield self.data2entry(data, item_id, profile) | |
478 ret = yield self._p.publish(service, node, [item], profile_key=profile) | |
368 defer.returnValue(ret) | 479 defer.returnValue(ret) |
369 | 480 |
481 | |
482 ## retract ## | |
483 | |
484 def _mbRetract(self, service_jid_s, nodeIdentifier, itemIdentifier, profile_key): | |
485 """Call self._p._retractItem, but use default node if node is empty""" | |
486 return self._p._retractItem(service_jid_s, nodeIdentifier or NS_MICROBLOG, itemIdentifier, True, profile_key) | |
487 | |
370 ## get ## | 488 ## get ## |
371 | 489 |
372 def _getLastMicroblogs(self, pub_jid_s, max_items=10, profile_key=C.PROF_KEY_NONE): | 490 def _mbGetLast(self, service_jid_s, node="", max_items=10, extra_dict=None, profile_key=C.PROF_KEY_NONE): |
373 return self.getLastMicroblogs(jid.JID(pub_jid_s), max_items, profile_key) | 491 """ |
492 @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit | |
493 """ | |
494 max_items = None if max_items == C.NO_LIMIT else max_items | |
495 extra = self._p.parseExtra(extra_dict) | |
496 return self.mbGetLast(jid.JID(service_jid_s), node or None, max_items, extra.rsm_request, extra.extra, profile_key) | |
374 | 497 |
375 @defer.inlineCallbacks | 498 @defer.inlineCallbacks |
376 def getLastMicroblogs(self, pub_jid, max_items=10, profile_key=C.PROF_KEY_NONE): | 499 def mbGetLast(self, service_jid, node=None, max_items=None, rsm_request=None, extra=None, profile_key=C.PROF_KEY_NONE): |
377 """Get the last published microblogs | 500 """Get the last published microblogs |
378 | 501 |
379 @param pub_jid(jid.JID): jid of the publisher | 502 @param service_jid(jid.JID): jid of the publisher |
380 @param max_items: how many microblogs we want to get | 503 @param node(unicode, None): node to get (or microblog node if None) |
504 @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit | |
505 @param rsm_request (rsm.RSMRequest): RSM request data | |
381 @param profile_key: profile key | 506 @param profile_key: profile key |
382 | 507 |
383 @return: a deferred couple with the list of items and metadatas. | 508 @return: a deferred couple with the list of items and metadatas. |
384 """ | 509 """ |
385 items_data = yield self._p.getItems(pub_jid, NS_MICROBLOG, max_items=max_items, profile_key=profile_key) | 510 if node is None: |
511 node = NS_MICROBLOG | |
512 items_data = yield self._p.getItems(service_jid, node, max_items=max_items, rsm_request=rsm_request, extra=extra, profile_key=profile_key) | |
386 serialised = yield self._p.serItemsDataD(items_data, self.item2mbdata) | 513 serialised = yield self._p.serItemsDataD(items_data, self.item2mbdata) |
387 defer.returnValue(serialised) | 514 defer.returnValue(serialised) |
388 | 515 |
389 def parseCommentUrl(self, node_url): | 516 def parseCommentUrl(self, node_url): |
390 """Parse a XMPP URI | 517 """Parse a XMPP URI |
408 | 535 |
409 return (service, node) | 536 return (service, node) |
410 | 537 |
411 ## configure ## | 538 ## configure ## |
412 | 539 |
413 def setMicroblogAccess(self, access="presence", profile_key=C.PROF_KEY_NONE): | 540 def mbSetAccess(self, access="presence", profile_key=C.PROF_KEY_NONE): |
414 """Create a microblog node on PEP with given access | 541 """Create a microblog node on PEP with given access |
415 | 542 |
416 If the node already exists, it change options | 543 If the node already exists, it change options |
417 @param access: Node access model, according to xep-0060 #4.5 | 544 @param access: Node access model, according to xep-0060 #4.5 |
418 @param profile_key: profile key""" | 545 @param profile_key: profile key""" |
489 publishers = None | 616 publishers = None |
490 elif publishers_type == C.JID: | 617 elif publishers_type == C.JID: |
491 publishers[:] = [jid.JID(publisher) for publisher in publishers] | 618 publishers[:] = [jid.JID(publisher) for publisher in publishers] |
492 return publishers_type, publishers | 619 return publishers_type, publishers |
493 | 620 |
621 | |
622 | |
494 # subscribe # | 623 # subscribe # |
495 | 624 |
496 def _mBSubscribeToMany(self, publishers_type, publishers, profile_key): | 625 def _mbSubscribeToMany(self, publishers_type, publishers, profile_key): |
497 """ | 626 """ |
498 | 627 |
499 @return (str): session id: Use pubsub.getSubscribeRTResult to get the results | 628 @return (str): session id: Use pubsub.getSubscribeRTResult to get the results |
500 """ | 629 """ |
501 publishers_type, publishers = self._checkPublishers(publishers_type, publishers) | 630 publishers_type, publishers = self._checkPublishers(publishers_type, publishers) |
502 return self.mBSubscribeToMany(publishers_type, publishers, profile_key) | 631 return self.mbSubscribeToMany(publishers_type, publishers, profile_key) |
503 | 632 |
504 def mBSubscribeToMany(self, publishers_type, publishers, profile_key): | 633 def mbSubscribeToMany(self, publishers_type, publishers, profile_key): |
505 """Subscribe microblogs for a list of groups or jids | 634 """Subscribe microblogs for a list of groups or jids |
506 | 635 |
507 @param publishers_type: type of the list of publishers, one of: | 636 @param publishers_type: type of the list of publishers, one of: |
508 C.ALL: get all jids from roster, publishers is not used | 637 C.ALL: get all jids from roster, publishers is not used |
509 C.GROUP: get jids from groups | 638 C.GROUP: get jids from groups |
515 client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key) | 644 client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key) |
516 return self._p.subscribeToMany(node_data, client.jid.userhostJID(), profile_key=profile_key) | 645 return self._p.subscribeToMany(node_data, client.jid.userhostJID(), profile_key=profile_key) |
517 | 646 |
518 # get # | 647 # get # |
519 | 648 |
520 def _mBGetFromManyRTResult(self, session_id, profile_key=C.PROF_KEY_DEFAULT): | 649 def _mbGetFromManyRTResult(self, session_id, profile_key=C.PROF_KEY_DEFAULT): |
521 """Get real-time results for mBGetFromMany session | 650 """Get real-time results for mbGetFromMany session |
522 | 651 |
523 @param session_id: id of the real-time deferred session | 652 @param session_id: id of the real-time deferred session |
524 @param return (tuple): (remaining, results) where: | 653 @param return (tuple): (remaining, results) where: |
525 - remaining is the number of still expected results | 654 - remaining is the number of still expected results |
526 - results is a list of tuple with | 655 - results is a list of tuple with |
527 - service (unicode): pubsub service | 656 - service (unicode): pubsub service |
528 - node (unicode): pubsub node | 657 - node (unicode): pubsub node |
529 - failure (unicode): empty string in case of success, error message else | 658 - failure (unicode): empty string in case of success, error message else |
530 - items_data(tuple): data tuple as returned by [getLastMicroblogs] | 659 - items_data(list): data as returned by [mbGetLast] |
660 - items_metadata(dict): metadata as returned by [mbGetLast] | |
531 @param profile_key: %(doc_profile_key)s | 661 @param profile_key: %(doc_profile_key)s |
532 """ | 662 """ |
533 def onSuccess(items_data): | 663 def onSuccess(items_data): |
534 """convert items elements to list of microblog data in items_data""" | 664 """convert items elements to list of microblog data in items_data""" |
535 d = self._p.serItemsDataD(items_data, self.item2mbdata) | 665 d = self._p.serItemsDataD(items_data, self.item2mbdata) |
544 d.addCallback(lambda ret: (ret[0], | 674 d.addCallback(lambda ret: (ret[0], |
545 [(service.full(), node, failure, items, metadata) | 675 [(service.full(), node, failure, items, metadata) |
546 for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()])) | 676 for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()])) |
547 return d | 677 return d |
548 | 678 |
549 def _mBGetFromMany(self, publishers_type, publishers, max_item=10, rsm_dict=None, profile_key=C.PROF_KEY_NONE): | 679 def _mbGetFromMany(self, publishers_type, publishers, max_items=10, extra_dict=None, profile_key=C.PROF_KEY_NONE): |
550 """ | 680 """ |
551 @param max_item(int): maximum number of item to get, C.NO_LIMIT for no limit | 681 @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit |
552 """ | 682 """ |
553 max_item = None if max_item == C.NO_LIMIT else max_item | 683 max_items = None if max_items == C.NO_LIMIT else max_items |
554 publishers_type, publishers = self._checkPublishers(publishers_type, publishers) | 684 publishers_type, publishers = self._checkPublishers(publishers_type, publishers) |
555 return self.mBGetFromMany(publishers_type, publishers, max_item, rsm.RSMRequest(**rsm_dict) if rsm_dict else None, profile_key) | 685 extra = self._p.parseExtra(extra_dict) |
556 | 686 return self.mbGetFromMany(publishers_type, publishers, max_items, extra.rsm_request, extra.extra, profile_key) |
557 def mBGetFromMany(self, publishers_type, publishers, max_item=None, rsm_data=None, profile_key=C.PROF_KEY_NONE): | 687 |
688 def mbGetFromMany(self, publishers_type, publishers, max_items=None, rsm_request=None, extra=None, profile_key=C.PROF_KEY_NONE): | |
558 """Get the published microblogs for a list of groups or jids | 689 """Get the published microblogs for a list of groups or jids |
559 | 690 |
560 @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL") | 691 @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL") |
561 @param publishers (list): list of publishers, according to publishers_type (list of groups or list of jids) | 692 @param publishers (list): list of publishers, according to publishers_type (list of groups or list of jids) |
562 @param max_items (int): optional limit on the number of retrieved items. | 693 @param max_items (int): optional limit on the number of retrieved items. |
563 @param rsm_data (rsm.RSMRequest): RSM request data, common to all publishers | 694 @param rsm_request (rsm.RSMRequest): RSM request data, common to all publishers |
695 @param extra (dict): Extra data | |
564 @param profile_key: profile key | 696 @param profile_key: profile key |
565 @return: a deferred dict with: | 697 @return (str): RT Deferred session id |
566 - key: publisher (unicode) | 698 """ |
567 - value: couple (list[dict], dict) with: | 699 # XXX: extra is unused here so far |
568 - the microblogs data | |
569 - RSM response data | |
570 """ | |
571 client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key) | 700 client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key) |
572 return self._p.getFromMany(node_data, max_item, rsm_data, profile_key=profile_key) | 701 return self._p.getFromMany(node_data, max_items, rsm_request, profile_key=profile_key) |
573 | 702 |
574 # comments # | 703 # comments # |
575 | 704 |
576 def _mBGetFromManyWithCommentsRTResult(self, session_id, profile_key=C.PROF_KEY_DEFAULT): | 705 def _mbGetFromManyWithCommentsRTResult(self, session_id, profile_key=C.PROF_KEY_DEFAULT): |
577 """Get real-time results for [mBGetFromManyWithComments] session | 706 """Get real-time results for [mbGetFromManyWithComments] session |
578 | 707 |
579 @param session_id: id of the real-time deferred session | 708 @param session_id: id of the real-time deferred session |
580 @param return (tuple): (remaining, results) where: | 709 @param return (tuple): (remaining, results) where: |
581 - remaining is the number of still expected results | 710 - remaining is the number of still expected results |
582 - results is a list of tuple with | 711 - results is a list of tuple with |
583 - service (unicode): pubsub service | 712 - service (unicode): pubsub service |
584 - node (unicode): pubsub node | 713 - node (unicode): pubsub node |
585 - success (bool): True if the getItems was successful | |
586 - failure (unicode): empty string in case of success, error message else | 714 - failure (unicode): empty string in case of success, error message else |
587 - items(list): list of items with: | 715 - items(list): list of items with: |
588 - item(dict): item microblog data | 716 - item(dict): item microblog data |
589 - comments_list(list): list of comments with | 717 - comments_list(list): list of comments with |
590 - service (unicode): pubsub service where the comments node is | 718 - service (unicode): pubsub service where the comments node is |
600 d.addCallback(lambda ret: (ret[0], | 728 d.addCallback(lambda ret: (ret[0], |
601 [(service.full(), node, failure, items, metadata) | 729 [(service.full(), node, failure, items, metadata) |
602 for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()])) | 730 for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()])) |
603 return d | 731 return d |
604 | 732 |
605 def _mBGetFromManyWithComments(self, publishers_type, publishers, max_item=10, max_comments=C.NO_LIMIT, rsm_dict=None, rsm_comments_dict=None, profile_key=C.PROF_KEY_NONE): | 733 def _mbGetFromManyWithComments(self, publishers_type, publishers, max_items=10, max_comments=C.NO_LIMIT, extra_dict=None, extra_comments_dict=None, profile_key=C.PROF_KEY_NONE): |
606 """ | 734 """ |
607 @param max_item(int): maximum number of item to get, C.NO_LIMIT for no limit | 735 @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit |
608 @param max_comments(int): maximum number of comments to get, C.NO_LIMIT for no limit | 736 @param max_comments(int): maximum number of comments to get, C.NO_LIMIT for no limit |
609 """ | 737 """ |
610 max_item = None if max_item == C.NO_LIMIT else max_item | 738 max_items = None if max_items == C.NO_LIMIT else max_items |
611 max_comments = None if max_comments == C.NO_LIMIT else max_comments | 739 max_comments = None if max_comments == C.NO_LIMIT else max_comments |
612 publishers_type, publishers = self._checkPublishers(publishers_type, publishers) | 740 publishers_type, publishers = self._checkPublishers(publishers_type, publishers) |
613 return self.mBGetFromManyWithComments(publishers_type, publishers, max_item, max_comments, | 741 extra = self._p.parseExtra(extra_dict) |
614 rsm.RSMRequest(**rsm_dict) if rsm_dict else None, | 742 extra_comments = self._p.parseExtra(extra_comments_dict) |
615 rsm.RSMRequest(**rsm_comments_dict) if rsm_comments_dict else None, | 743 return self.mbGetFromManyWithComments(publishers_type, publishers, max_items, max_comments, |
744 extra.rsm_request, | |
745 extra.extra, | |
746 extra_comments.rsm_request, | |
747 extra_comments.extra, | |
616 profile_key) | 748 profile_key) |
617 | 749 |
618 def mBGetFromManyWithComments(self, publishers_type, publishers, max_item=None, max_comments=None, rsm_request=None, rsm_comments=None, profile_key=C.PROF_KEY_NONE): | 750 def mbGetFromManyWithComments(self, publishers_type, publishers, max_items=None, max_comments=None, rsm_request=None, extra=None, rsm_comments=None, extra_comments=None, profile_key=C.PROF_KEY_NONE): |
619 """Helper method to get the microblogs and their comments in one shot | 751 """Helper method to get the microblogs and their comments in one shot |
620 | 752 |
621 @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL") | 753 @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL") |
622 @param publishers (list): list of publishers, according to publishers_type (list of groups or list of jids) | 754 @param publishers (list): list of publishers, according to publishers_type (list of groups or list of jids) |
623 @param max_items (int): optional limit on the number of retrieved items. | 755 @param max_items (int): optional limit on the number of retrieved items. |
624 @param max_comments (int): maximum number of comments to retrieve | 756 @param max_comments (int): maximum number of comments to retrieve |
625 @param rsm_request (rsm.RSMRequest): RSM request for initial items only | 757 @param rsm_request (rsm.RSMRequest): RSM request for initial items only |
758 @param extra (dict): extra configuration for initial items only | |
626 @param rsm_comments (rsm.RSMRequest): RSM request for comments only | 759 @param rsm_comments (rsm.RSMRequest): RSM request for comments only |
760 @param extra_comments (dict): extra configuration for comments only | |
627 @param profile_key: profile key | 761 @param profile_key: profile key |
628 @return: a deferred dict with: | 762 @return (str): RT Deferred session id |
629 - key: publisher (unicode) | |
630 - value: couple (list[dict], dict) with: | |
631 - the microblogs data | |
632 - RSM response data | |
633 """ | 763 """ |
634 # XXX: this method seems complicated because it do a couple of treatments | 764 # XXX: this method seems complicated because it do a couple of treatments |
635 # to serialise and associate the data, but it make life in frontends side | 765 # to serialise and associate the data, but it make life in frontends side |
636 # a lot easier | 766 # a lot easier |
637 | 767 |
651 if key.startswith('comments') and key.endswith('_service'): | 781 if key.startswith('comments') and key.endswith('_service'): |
652 prefix = key[:key.find('_')] | 782 prefix = key[:key.find('_')] |
653 service_s = value | 783 service_s = value |
654 node = item["{}{}".format(prefix, "_node")] | 784 node = item["{}{}".format(prefix, "_node")] |
655 # time to get the comments | 785 # time to get the comments |
656 d = self._p.getItems(jid.JID(service_s), node, max_comments, rsm_request=rsm_comments, profile_key=profile_key) | 786 d = self._p.getItems(jid.JID(service_s), node, max_comments, rsm_request=rsm_comments, extra=extra_comments, profile_key=profile_key) |
657 # then serialise | 787 # then serialise |
658 d.addCallback(lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata)) | 788 d.addCallback(lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata)) |
659 # with failure handling | 789 # with failure handling |
660 d.addCallback(lambda serialised_items_data: ('',) + serialised_items_data) | 790 d.addCallback(lambda serialised_items_data: ('',) + serialised_items_data) |
661 d.addErrback(lambda failure: (unicode(failure.value), [], {})) | 791 d.addErrback(lambda failure: (unicode(failure.value), [], {})) |
662 # and associate with service/node (needed if there are several comments nodes) | 792 # and associate with service/node (needed if there are several comments nodes) |
663 d.addCallback(lambda serialised: (service_s, node) + serialised) | 793 d.addCallback(lambda serialised, service_s=service_s, node=node: (service_s, node) + serialised) |
664 dlist.append(d) | 794 dlist.append(d) |
665 # we get the comments | 795 # we get the comments |
666 comments_d = defer.gatherResults(dlist) | 796 comments_d = defer.gatherResults(dlist) |
667 # and add them to the item data | 797 # and add them to the item data |
668 comments_d.addCallback(lambda comments_data: (item, comments_data)) | 798 comments_d.addCallback(lambda comments_data, item=item: (item, comments_data)) |
669 items_dlist.append(comments_d) | 799 items_dlist.append(comments_d) |
670 # we gather the items + comments in a list | 800 # we gather the items + comments in a list |
671 items_d = defer.gatherResults(items_dlist) | 801 items_d = defer.gatherResults(items_dlist) |
672 # and add the metadata | 802 # and add the metadata |
673 items_d.addCallback(lambda items: (items, metadata)) | 803 items_d.addCallback(lambda items_completed: (items_completed, metadata)) |
674 return items_d | 804 return items_d |
675 | 805 |
676 client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key) | 806 client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key) |
677 deferreds = {} | 807 deferreds = {} |
678 for service, node in node_data: | 808 for service, node in node_data: |
679 d = deferreds[(service, node)] = self._p.getItems(service, node, max_item, rsm_request=rsm_request, profile_key=profile_key) | 809 d = deferreds[(service, node)] = self._p.getItems(service, node, max_items, rsm_request=rsm_request, extra=extra, profile_key=profile_key) |
680 d.addCallback(lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata)) | 810 d.addCallback(lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata)) |
681 d.addCallback(getComments) | 811 d.addCallback(getComments) |
682 d.addCallback(lambda items_comments_data: ('', items_comments_data)) | 812 d.addCallback(lambda items_comments_data: ('', items_comments_data)) |
683 d.addErrback(lambda failure: (unicode(failure.value), ([],{}))) | 813 d.addErrback(lambda failure: (unicode(failure.value), ([],{}))) |
684 | 814 |