diff src/server/blog.py @ 449:981ed669d3b3

/!\ reorganize all the file hierarchy, move the code and launching script to src: - browser_side --> src/browser - public --> src/browser_side/public - libervia.py --> src/browser/libervia_main.py - libervia_server --> src/server - libervia_server/libervia.sh --> src/libervia.sh - twisted --> src/twisted - new module src/common - split constants.py in 3 files: - src/common/constants.py - src/browser/constants.py - src/server/constants.py - output --> html (generated by pyjsbuild during the installation) - new option/parameter "data_dir" (-d) to indicates the directory containing html and server_css - setup.py installs libervia to the following paths: - src/common --> <LIB>/libervia/common - src/server --> <LIB>/libervia/server - src/twisted --> <LIB>/twisted - html --> <SHARE>/libervia/html - server_side --> <SHARE>libervia/server_side - LIBERVIA_INSTALL environment variable takes 2 new options with prompt confirmation: - clean: remove previous installation directories - purge: remove building and previous installation directories You may need to update your sat.conf and/or launching script to update the following options/parameters: - ssl_certificate - data_dir
author souliane <souliane@mailoo.org>
date Tue, 20 May 2014 06:41:16 +0200
parents libervia_server/blog.py@c406e46fe9c0
children 3ef6ce200c27
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/blog.py	Tue May 20 06:41:16 2014 +0200
@@ -0,0 +1,226 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Libervia: a Salut à Toi frontend
+# Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from sat.core.i18n import _
+from sat_frontends.tools.strings import addURLToText
+
+from twisted.internet import defer
+from twisted.web import server
+from twisted.web.resource import Resource
+from twisted.words.protocols.jabber.jid import JID
+from datetime import datetime
+import uuid
+import re
+
+from libervia.server.html_tools import sanitizeHtml
+from libervia.server.constants import Const as C
+
+
+class MicroBlog(Resource):
+    isLeaf = True
+
+    ERROR_TEMPLATE = """
+                <html>
+                <head profile="http://www.w3.org/2005/10/profile">
+                    <link rel="icon" type="image/png" href="%(root)ssat_logo_16.png">
+                    <title>MICROBLOG ERROR</title>
+                </head>
+                <body>
+                    <h1 style='text-align: center; color: red;'>%(message)s</h1>
+                </body>
+                </html>
+                """
+
+    def __init__(self, host):
+        self.host = host
+        Resource.__init__(self)
+
+    def render_GET(self, request):
+        if not request.postpath:
+            return MicroBlog.ERROR_TEMPLATE % {'root': '',
+                                               'message': "You must indicate a nickname"}
+        else:
+            prof_requested = request.postpath[0]
+            #TODO: char check: only use alphanumerical chars + some extra(_,-,...) here
+            prof_found = self.host.bridge.getProfileName(prof_requested)
+            if not prof_found or prof_found == 'libervia':
+                return MicroBlog.ERROR_TEMPLATE % {'root': '../' * len(request.postpath),
+                                                    'message': "Invalid nickname"}
+            else:
+                def got_jid(pub_jid_s):
+                    pub_jid = JID(pub_jid_s)
+                    d2 = defer.Deferred()
+                    item_id = None
+                    if len(request.postpath) > 1:
+                        if request.postpath[1] == 'atom.xml':  # return the atom feed
+                            d2.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, prof_found], None)
+                            self.host.bridge.getLastGroupBlogsAtom(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback)
+                            return
+                        try:  # check if the given path is a valid UUID
+                            uuid.UUID(request.postpath[1])
+                            item_id = request.postpath[1]
+                        except ValueError:
+                            pass
+                    d2.addCallbacks(self.render_html_blog, self.render_error_blog, [request, prof_found], None, [request, prof_found], None)
+                    if item_id:  # display one message and its comments
+                        self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [item_id], 'libervia', d2.callback, d2.errback)
+                    else:  # display the last messages without comment
+                        self.host.bridge.getLastGroupBlogs(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback)
+
+                d1 = defer.Deferred()
+                JID(self.host.bridge.asyncGetParamA('JabberID', 'Connection', 'value', C.SERVER_SECURITY_LIMIT, prof_found, callback=d1.callback, errback=d1.errback))
+                d1.addCallbacks(got_jid)
+
+                return server.NOT_DONE_YET
+
+    def render_html_blog(self, mblog_data, request, profile):
+        """Retrieve the user parameters before actually rendering the static blog
+        @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data)
+        @param request: HTTP request
+        @param profile
+        """
+        d_list = []
+        style = {}
+
+        def getCallback(param_name):
+            d = defer.Deferred()
+            d.addCallback(lambda value: style.update({param_name: value}))
+            d_list.append(d)
+            return d.callback
+
+        eb = lambda failure: self.render_error_blog(failure, request, profile)
+
+        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, style, request, profile)
+        defer.DeferredList(d_list).addCallback(cb)
+
+    def __render_html_blog(self, mblog_data, style, 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 of microblog data or list of couple (microblog data, list of microblog data)
+        @param style: dict defining the blog's rendering parameters
+        @param request: the HTTP request
+        @profile
+        """
+        if not isinstance(style, dict):
+            style = {}
+        user = sanitizeHtml(profile).encode('utf-8')
+        root_url = '../' * len(request.postpath)
+        base_url = root_url + 'blog/' + user
+
+        def getFromData(key):
+            return sanitizeHtml(style[key]).encode('utf-8') if key in style else ''
+
+        def getImageFromData(key, alt):
+            """regexp from http://answers.oreilly.com/topic/280-how-to-validate-urls-with-regular-expressions/"""
+            url = style[key].encode('utf-8') if key in style else ''
+            regexp = r"^(https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+(/[\w-]+)*/[\w-]+\.(gif|png|jpg)$"
+            return "<img src='%(url)s' alt='%(alt)s'/>" % {'alt': alt, 'url': url} if re.match(regexp, url) else alt
+
+        request.write("""
+            <html>
+            <head>
+                <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+                <meta name="keywords" content="%(keywords)s">
+                <meta name="description" content="%(description)s">
+                <link rel="alternate" type="application/atom+xml" href="%(base)s/atom.xml"/>
+                <link rel="stylesheet" type="text/css" href="%(root)scss/blog.css" />
+                <link rel="icon" type="image/png" href="%(root)ssat_logo_16.png">
+                <title>%(title)s</title>
+            </head>
+            <body>
+            <div class="mblog_title"><a href="%(base)s">%(banner_elt)s</a></div>
+            """ % {'base': base_url,
+                   'root': root_url,
+                   'user': user,
+                   'keywords': getFromData(C.STATIC_BLOG_PARAM_KEYWORDS),
+                   'description': getFromData(C.STATIC_BLOG_PARAM_DESCRIPTION),
+                   'title': getFromData(C.STATIC_BLOG_PARAM_TITLE) or "%s's microblog" % user,
+                   'banner_elt': getImageFromData(C.STATIC_BLOG_PARAM_BANNER, user)})
+        mblog_data = [(entry if isinstance(entry, tuple) else (entry, [])) for entry in mblog_data]
+        mblog_data = sorted(mblog_data, key=lambda entry: (-float(entry[0].get('published', 0))))
+        for entry in mblog_data:
+            self.__render_html_entry(entry[0], base_url, request)
+            comments = sorted(entry[1], key=lambda entry: (float(entry.get('published', 0))))
+            for comment in comments:
+                self.__render_html_entry(comment, base_url, request)
+        request.write('</body></html>')
+        request.finish()
+
+    def __render_html_entry(self, entry, base_url, request):
+        """Render one microblog entry.
+        @param entry: the microblog entry
+        @param base_url: the base url of the blog
+        @param request: the HTTP request
+        """
+        timestamp = float(entry.get('published', 0))
+        datetime_ = datetime.fromtimestamp(timestamp)
+        is_comment = entry['type'] == 'comment'
+        if is_comment:
+            author = (_("comment from %s") % entry['author']).encode('utf-8')
+            item_link = ''
+        else:
+            author = '&nbsp;'
+            item_link = ("%(base)s/%(item_id)s" % {'base': base_url, 'item_id': entry['id']}).encode('utf-8')
+
+        def getText(key):
+            if ('%s_xhtml' % key) in entry:
+                return entry['%s_xhtml' % key].encode('utf-8')
+            elif key in entry:
+                processor = addURLToText if key.startswith('content') else sanitizeHtml
+                return processor(entry[key]).encode('utf-8')
+            return ''
+
+        def addMainItemLink(elem):
+            if not item_link or not elem:
+                return elem
+            return """<a href="%(link)s" class="item_link">%(elem)s</a>""" % {'link': item_link, 'elem': elem}
+
+        header = addMainItemLink("""<div class="mblog_header">
+                                      <div class="mblog_metadata">
+                                        <div class="mblog_author">%(author)s</div>
+                                        <div class="mblog_timestamp">%(date)s</div>
+                                      </div>
+                                    </div>""" % {'author': author, 'date': datetime_})
+
+        title = addMainItemLink(getText('title'))
+        body = getText('content')
+        if title:  # insert the title within the body
+            body = """<h1>%(title)s</h1>\n%(body)s""" % {'title': title, 'body': body}
+
+        request.write("""<div class="mblog_entry %(extra_style)s">
+                           %(header)s
+                           <span class="mblog_content">%(content)s</span>
+                         </div>""" %
+                         {'extra_style': 'mblog_comment' if entry['type'] == 'comment' else '',
+                          'item_link': item_link,
+                          'header': header,
+                          'content': body})
+
+    def render_atom_feed(self, feed, request):
+        request.write(feed.encode('utf-8'))
+        request.finish()
+
+    def render_error_blog(self, error, request, profile):
+        request.write(MicroBlog.ERROR_TEMPLATE % {'root': '../' * len(request.postpath),
+                                                  'message': "Can't access requested data"})
+        request.finish()