diff src/server/blog.py @ 725:c1abaa91a121

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
author souliane <souliane@mailoo.org>
date Thu, 10 Sep 2015 15:17:18 +0200
parents 29b84af2ff7b
children e949b7c7ed9c
line wrap: on
line diff
--- 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):