# HG changeset patch # User souliane # Date 1441891038 -7200 # Node ID c1abaa91a121644c402f0b8582e4a088c61e870b # Parent 994be887e843facc65205a6ca82d2ce984b6652c server (blog): update to the new mechanism in XEP-0277 + improvement: - use the new bridge methods mbGetFromManyWithComments and mbGet - use PEP namespace - add dedicated URL parameter "comments_max" instead of dirty hack diff -r 994be887e843 -r c1abaa91a121 src/server/blog.py --- a/src/server/blog.py Thu Sep 10 07:40:59 2015 +0200 +++ b/src/server/blog.py Thu Sep 10 15:17:18 2015 +0200 @@ -38,6 +38,9 @@ from libervia.server.constants import Const as C +NS_MICROBLOG = 'urn:xmpp:microblog:0' + + class TemplateProcessor(object): THEME = 'default' @@ -149,29 +152,27 @@ def gotJID(self, pub_jid_s, request, profile): pub_jid = JID(pub_jid_s) - d = defer.Deferred() self.parseURLParams(request) - item_id, rsm_ = request.item_id, request.rsm_data - max_items = int(rsm_['max_']) + if request.item_id: + item_ids = [request.item_id] + max_items = 1 + else: + item_ids = [] + max_items = int(request.extra_dict['rsm_max']) if request.atom: - d.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, profile], None) - self.host.bridge.getGroupBlogsAtom(pub_jid.userhost(), rsm_, C.SERVICE_PROFILE, d.callback, d.errback) - return - - d.addCallbacks(self.render_html_blog, self.render_error_blog, [request, profile], None, [request, profile], None) - if item_id: - if max_items > 0: # display one message and its comments - self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [item_id], {}, max_items, C.SERVICE_PROFILE, d.callback, d.errback) - else: # display one message, count its comments - self.host.bridge.getGroupBlogs(pub_jid.userhost(), [item_id], {}, True, C.SERVICE_PROFILE, d.callback, d.errback) + self.host.bridge.mbGetAtom(pub_jid.userhost(), NS_MICROBLOG, max_items, item_ids, + request.extra_dict, C.SERVICE_PROFILE, + lambda feed: self.render_atom_feed(feed, request), + lambda failure: self.render_error_blog(failure, request, profile)) + elif request.item_id: + self.getItemById(pub_jid, request.item_id, request.extra_dict, + request.extra_comments_dict, request, profile) else: - if max_items == 1: # display one message and its comments - self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [], rsm_, C.RSM_MAX_COMMENTS, C.SERVICE_PROFILE, d.callback, d.errback) - else: # display the last messages, count their comments - self.host.bridge.getGroupBlogs(pub_jid.userhost(), [], rsm_, True, C.SERVICE_PROFILE, d.callback, d.errback) - + self.getItems(pub_jid, max_items, request.extra_dict, + request.extra_comments_dict, request, profile) + def parseURLParams(self, request): """Parse the request URL parameters. @@ -191,38 +192,122 @@ pass self.parseURLParamsRSM(request) - request.display_single = (request.item_id is not None) or int(request.rsm_data['max_']) == 1 + request.display_single = (request.item_id is not None) or int(request.extra_dict['rsm_max']) == 1 + self.parseURLParamsCommentsRSM(request) def parseURLParamsRSM(self, request): - """Parse RSM request data from the URL parameters. + """Parse RSM request data from the URL parameters for main items @param request: HTTP request """ - rsm_ = {} + request.extra_dict = {} + if request.item_id: # XXX: item_id and RSM are not compatible + return try: - rsm_['max_'] = request.args['max'][0] + request.extra_dict['rsm_max'] = request.args['max'][0] except (ValueError, KeyError): - rsm_['max_'] = unicode(C.RSM_MAX_ITEMS if request.item_id else C.RSM_MAX_COMMENTS) + request.extra_dict['rsm_max'] = unicode(C.RSM_MAX_ITEMS) try: - rsm_['index'] = request.args['index'][0] + request.extra_dict['rsm_index'] = request.args['index'][0] except (ValueError, KeyError): try: - rsm_['before'] = request.args['before'][0] + request.extra_dict['rsm_before'] = request.args['before'][0] except KeyError: try: - rsm_['after'] = request.args['after'][0] + request.extra_dict['rsm_after'] = request.args['after'][0] except KeyError: pass - request.rsm_data = rsm_ + + def parseURLParamsCommentsRSM(self, request): + """Parse RSM request data from the URL parameters for comments + + @param request: HTTP request + """ + request.extra_comments_dict = {} + if request.display_single: + try: + request.extra_comments_dict['rsm_max'] = request.args['comments_max'][0] + except (ValueError, KeyError): + request.extra_comments_dict['rsm_max'] = unicode(C.RSM_MAX_COMMENTS) + else: + request.extra_comments_dict['rsm_max'] = "0" + + def getItemById(self, pub_jid, item_id, extra_dict, extra_comments_dict, request, profile): + """ + + @param pub_jid (jid.JID): publisher JID + @param item_id(unicode): ID of the item to retrieve + @param extra_dict (dict): extra configuration for initial items only + @param extra_comments_dict (dict): extra configuration for comments only + @param request: HTTP request + @param profile + """ + + def gotItems(items): + items, metadata = items + item = items[0] # assume there's only one item + + def gotCount(items_bis): + metadata_bis = items_bis[1] + metadata['rsm_count'] = metadata_bis['rsm_count'] + index_key = "rsm_index" if metadata_bis.get("rsm_index") else "rsm_count" + metadata['rsm_index'] = unicode(int(metadata_bis[index_key]) - 1) + metadata['rsm_first'] = metadata['rsm_last'] = item["id"] + + def gotComments(comments): + # build the items as self.getItems would do it (and as self.render_html_blog expects them to be) + comments = [(item['comments_service'], item['comments_node'], "", comments[0], comments[1])] + self.render_html_blog([(item, comments)], metadata, request, profile) - def render_html_blog(self, mblog_data, request, profile): + # get the comments + max_comments = int(extra_comments_dict['rsm_max']) + self.host.bridge.mbGet(item['comments_service'], item['comments_node'], max_comments, [], + extra_comments_dict, C.SERVICE_PROFILE, callback=gotComments) + + # XXX: retrieve RSM information related to the main item. We can't do it while + # retrieving the item, because item_ids and rsm should not be used together. + self.host.bridge.mbGet(pub_jid.userhost(), NS_MICROBLOG, 1, [], + {"rsm_max": "1", "rsm_after": item["id"]}, C.SERVICE_PROFILE, callback=gotCount) + + # get the main item + self.host.bridge.mbGet(pub_jid.userhost(), NS_MICROBLOG, 1, [item_id], + extra_dict, C.SERVICE_PROFILE, callback=gotItems) + + def getItems(self, pub_jid, max_items, extra_dict, extra_comments_dict, request, profile): + """ + + @param pub_jid (jid.JID): publisher JID + @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit + @param extra_dict (dict): extra configuration for initial items only + @param extra_comments_dict (dict): extra configuration for comments only + @param request: HTTP request + @param profile + """ + def getResultCb(data, rt_session): + remaining, results = data + for result in results: + service, node, failure, items, metadata = result + if not failure: + self.render_html_blog(items, metadata, request, profile) + + if remaining: + self._getResults(rt_session) + + def getResult(rt_session): + self.host.bridge.mbGetFromManyWithCommentsRTResult(rt_session, C.SERVICE_PROFILE, + callback=lambda data: getResultCb(data, rt_session), + errback=lambda failure: self.render_error_blog(failure, request, profile)) + + max_comments = int(extra_comments_dict['rsm_max']) + self.host.bridge.mbGetFromManyWithComments(C.JID, [pub_jid.userhost()], max_items, + max_comments, extra_dict, extra_comments_dict, + C.SERVICE_PROFILE, callback=getResult) + + def render_html_blog(self, items, metadata, request, profile): """Retrieve the user parameters before actually rendering the static blog - @param mblog_data (list): couple (list, dict) with: - - a list of microblog data, or a list of couple containing: - - microblog data (main item) - - couple (comments data, RSM response data for the comments) - - RSM response data for the main items + @param items(list[tuple(dict, list)]): same as in self.__render_html_blog + @param metadata(dict): original node metadata @param request: HTTP request @param profile """ @@ -241,22 +326,28 @@ for param_name in (C.STATIC_BLOG_PARAM_TITLE, C.STATIC_BLOG_PARAM_BANNER, C.STATIC_BLOG_PARAM_KEYWORDS, C.STATIC_BLOG_PARAM_DESCRIPTION): self.host.bridge.asyncGetParamA(param_name, C.STATIC_BLOG_KEY, 'value', C.SERVER_SECURITY_LIMIT, profile, callback=getCallback(param_name), errback=eb) - cb = lambda dummy: self.__render_html_blog(mblog_data, options, request, profile) + cb = lambda dummy: self.__render_html_blog(items, metadata, options, request, profile) defer.DeferredList(d_list).addCallback(cb) - def __render_html_blog(self, mblog_data, options, request, profile): - """Actually render the static blog. If mblog_data is a list of dict, we are missing - the comments items so we just display the main items. If mblog_data is a list of couple, - each couple is associating a main item data with the list of its comments, so we render all. + def __render_html_blog(self, items, metadata, options, request, profile): + """Actually render the static blog. + + If mblog_data is a list of dict, we are missing the comments items so we just + display the main items. If mblog_data is a list of couple, each couple is + associating a main item data with the list of its comments, so we render all. - @param mblog_data (list): couple (list, dict) with: - - a list of microblog data, or a list of couple containing: - - microblog data (main item) - - couple (comments data, RSM response data for the comments) - - RSM response data for the main items + @param items(list[tuple(dict, list)]): list of 2-tuple with + - item(dict): item microblog data + - comments_list(list[tuple]): list of 5-tuple with + - service (unicode): pubsub service where the comments node is + - node (unicode): comments node + - failure (unicode): empty in case of success, else error message + - comments(list[dict]): list of microblog data + - comments_metadata(dict): metadata of the comment node + @param metadata(dict): original node metadata @param options: dict defining the blog's parameters @param request: the HTTP request - @profile + @param profile """ if not isinstance(options, dict): options = {} @@ -287,12 +378,20 @@ 'banner_img': getImageParams(C.STATIC_BLOG_PARAM_BANNER, avatar, title) } - mblog_data, main_rsm = mblog_data - mblog_data = [(entry if isinstance(entry, tuple) else (entry, ([], {}))) for entry in mblog_data] - mblog_data.sort(key=lambda entry: (-float(entry[0].get('updated', 0)))) + items.sort(key=lambda entry: (-float(entry[0].get('updated', 0)))) - data['navlinks'] = NavigationLinks(request, mblog_data, main_rsm, base_url) - data['messages'] = [BlogMessage(request, base_url, entry, comments[0]) for entry, comments in mblog_data] + data['navlinks'] = NavigationLinks(request, items, metadata, base_url) + data['messages'] = [] + for item in items: + item, comments_list = item + comments, comments_count = [], 0 + for node_comments in comments_list: + comments.extend(node_comments[3]) + try: + comments_count += int(node_comments[4]['rsm_count']) + except KeyError: + pass + data['messages'].append(BlogMessage(request, base_url, item, comments, comments_count)) request.write(self.useTemplate(request, 'static_blog', data)) request.finish() @@ -308,33 +407,33 @@ class NavigationLinks(object): - def __init__(self, request, mblog_data, rsm_data, base_url): + def __init__(self, request, items, rsm_data, base_url): """Build the navigation links. - @param mblog_data (dict): the microblogs that are displayed on the page + @param items (list): list of items @param rsm_data (dict): rsm data @param base_url (unicode): the base URL for this user's blog @return: dict """ for key in ('later_message', 'later_messages', 'older_message', 'older_messages'): - count = int(rsm_data.get('count', 0)) + count = int(rsm_data.get('rsm_count', 0)) setattr(self, key, '') # key must exist when using the template if count <= 0 or (request.display_single == key.endswith('s')): continue - index = int(rsm_data['index']) + index = int(rsm_data['rsm_index']) link_data = {'base_url': base_url, 'suffix': ''} if key.startswith('later_message'): if index <= 0: continue - link_data['item_id'] = rsm_data['first'] + link_data['item_id'] = rsm_data['rsm_first'] link_data['post_arg'] = 'before' else: - if index + len(mblog_data) >= count: + if index + len(items) >= count: continue - link_data['item_id'] = rsm_data['last'] + link_data['item_id'] = rsm_data['rsm_last'] link_data['post_arg'] = 'after' if request.display_single: @@ -362,20 +461,23 @@ class BlogMessage(object): - def __init__(self, request, base_url, entry, comments=None): + def __init__(self, request, base_url, entry, comments=None, comments_count=0): """ @param request: HTTP request @param base_url (unicode): the base URL - @param entry (dict{unicode:unicode]): microblog entry received from the backend - @param comments (list[dict]): comments + @param entry(dict): item microblog data + @param comments(list[dict]): list of microblog data + @param comments_count (int): total number of comments """ timestamp = float(entry.get('published', 0)) - is_comment = entry['type'] == 'comment' + + # FIXME: for now we assume that the comments' depth is only 1 + is_comment = not entry.get('comments', False) self.date = datetime.fromtimestamp(timestamp) - self.type = entry['type'] - self.style = 'mblog_comment' if entry['type'] == 'comment' else '' + self.type = "comment" if is_comment else "main_item" + self.style = 'mblog_comment' if is_comment else '' self.content = self.getText(entry, 'content') if is_comment: @@ -385,20 +487,19 @@ self.url = (u"%s/%s" % (base_url, entry['id'])).encode('utf-8') self.title = self.getText(entry, 'title') - comments_count = int(entry['comments_count']) count_text = lambda count: D_('comments') if count > 1 else D_('comment') self.comments_text = "%s %s" % (comments_count, count_text(comments_count)) delta = comments_count - len(comments) if request.display_single and delta > 0: - prev_url = "%s?max=%s" % (self.url, entry['comments_count']) + prev_url = "%s?comments_max=%s" % (self.url, unicode(comments_count)) prev_text = D_("show %(count)d previous %(comments)s") % \ {'count': delta, 'comments': count_text(delta)} self.all_comments_link = BlogLink(prev_url, "comments_link", prev_text) if comments: - comments.sort(key=lambda entry: float(entry.get('published', 0))) + comments.sort(key=lambda comment: float(comment.get('published', 0))) self.comments = [BlogMessage(request, base_url, comment) for comment in comments] def getText(self, entry, key):