[Xfce4-commits] <xfce-buildbot-scripts:master> Merge branch 'master' of ssh://git.xfce.org/git/admin/xfce-buildbot-scripts

Samuel Verstraete noreply at xfce.org
Thu Oct 22 16:26:03 CEST 2009


Updating branch refs/heads/master
         to b59690790494da574b40a392bacbb3553e395911 (commit)
       from 83db59a94d630b717507e4a2cc3c4d165e369777 (commit)

commit b59690790494da574b40a392bacbb3553e395911
Merge: 83db59a94d630b717507e4a2cc3c4d165e369777 bae8f92471969e67837809539cb33145fe3fdf56
Author: Samuel Verstraete <samuel.verstraete at gmail.com>
Date:   Thu Oct 22 16:25:24 2009 +0200

    Merge branch 'master' of ssh://git.xfce.org/git/admin/xfce-buildbot-scripts

commit bae8f92471969e67837809539cb33145fe3fdf56
Author: Enrico Tröger <enrico.troeger at uvena.de>
Date:   Mon Oct 12 20:19:13 2009 +0200

    Fix output if the layout cookie was set

commit 6c3d5f6a08b358cb54ab50eda63c3ce639b0c698
Author: Enrico Tröger <enrico.troeger at uvena.de>
Date:   Sun Oct 11 23:42:21 2009 +0200

    Implement XMLRPC service to distribute build requests sent by the GIT hook script to the appropriate buildbot instances
    
    The new buildbot infrastructure will use separate buildbot instances
    for each module because each module has its own GIT repository but
    Buildbot seems to not support such an infrastructure.
    To be able to keep one buildbot hook script for all GIT repositories,
    this hook script sends the change request to this service which then
    forward the request to the approrpiate buildbot instance.

commit 091dedc7629ed5f1a34cd2e535fd56e137eeb53d
Author: Enrico Tröger <enrico.troeger at uvena.de>
Date:   Sun Oct 11 21:32:14 2009 +0200

    Display the build platform in the build details

commit 7bb2bafaa8b2de99b705bb774f8af5af7676e83f
Author: Enrico Tröger <enrico.troeger at uvena.de>
Date:   Sun Oct 11 21:25:42 2009 +0200

    Don't pass more information to detail sites than necessary
    
    We don't need to know the exact build number when querying the detail status as we always want to see the latest build.
    Instead display the build number in the list of build details.

commit bb29c2ab4d07a1089fbf8d745a62e90a2d9ddda2
Author: Enrico Tröger <enrico.troeger at uvena.de>
Date:   Sat Oct 10 00:18:43 2009 +0200

    Store and retrieve the chosen layout from cookies

commit cedc1802520b88550c8761b91e24196456c2c95d
Author: Enrico Tröger <enrico.troeger at uvena.de>
Date:   Fri Oct 9 23:43:55 2009 +0200

    Major UI changes
    
    - Implement a very basic HTML generator
    - Use the default Xfce website header and CSS files for a more unique layout

commit 62de4bbbc0be59b1658d0ad5ba73890c32b64f8a
Author: Enrico Tröger <enrico.troeger at uvena.de>
Date:   Thu Oct 8 20:47:20 2009 +0200

    Fix missing closing tag, oops.

commit de8c0581a132a6ac78b6fcfd3f19dd72f8292ec6
Author: Enrico Tröger <enrico.troeger at uvena.de>
Date:   Thu Oct 8 20:42:53 2009 +0200

    Pretty print in debug mode

commit f72e47ca51bc9a8f4efa85db66b1649fab4fc13a
Author: Enrico Tröger <enrico.troeger at uvena.de>
Date:   Thu Oct 8 20:36:43 2009 +0200

    Use localhost to query the buildbot XMLRPC

commit a23946488d779aa3fabbf01bb3e8580e61c4d374
Author: Enrico Tröger <enrico.troeger at uvena.de>
Date:   Thu Oct 8 20:35:40 2009 +0200

    Lots of fixes
    
    - Remove the source link as the script is now maintained in the GIT repository
    - On the overview page, display only the short GIT revision
    - Fix URI and arguments parsing
    - Improve debug mode, to be enabled by appending ?debug

 xfbuildservice/buildbot_client.py  |   67 ++++++
 xfbuildservice/contants.py         |   12 +
 xfbuildservice/doc.txt             |   87 ++++++++
 xfbuildservice/xfbuildservice.conf |    6 +
 xfbuildservice/xfbuildservice.py   |   94 ++++++++
 xfcebuildstatus.py                 |  412 +++++++++++++++++++++++++++---------
 6 files changed, 578 insertions(+), 100 deletions(-)

diff --git a/xfbuildservice/buildbot_client.py b/xfbuildservice/buildbot_client.py
new file mode 100644
index 0000000..ed852be
--- /dev/null
+++ b/xfbuildservice/buildbot_client.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#       Part of the Xfce Buildbot Service
+#       Send data to a buildbot instance to trigger a new build. The data is received
+#       via a XMLRPC service which is called from GIT hook scripts.
+#
+#       Copyright 2009 Enrico Tröger <enrico(at)xfce(dot)org>
+#
+#       This program is free software; you can redistribute it and/or modify
+#       it under the terms of the GNU General Public License as published by
+#       the Free Software Foundation; either version 2 of the License.
+#
+#       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 General Public License for more details.
+#
+#       You should have received a copy of the GNU General Public License
+#       along with this program; if not, write to the Free Software
+#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# Largely based on Buildbot's contrib/git_buildbot.py script, mainly stripped down unneeded parts
+
+from twisted.spread import pb
+from twisted.cred import credentials
+from twisted.internet import reactor
+from optparse import OptionParser
+
+changes = []
+
+#----------------------------------------------------------------------
+def send_changes(host, port, user, passwd, _changes):
+    global changes
+
+    changes = _changes
+
+    # perform login
+    f = pb.PBClientFactory()
+    d = f.login(credentials.UsernamePassword(user, passwd))
+    reactor.connectTCP(host, port, f)
+
+    # connect callbacks and start
+    d.addCallback(connected)
+    d.addBoth(cleanup)
+    reactor.run()
+
+#----------------------------------------------------------------------
+def addChange(dummy, remote, changei):
+    try:
+        c = changei.next()
+    except StopIteration:
+        remote.broker.transport.loseConnection()
+        return None
+
+    d = remote.callRemote('addChange', c)
+    d.addCallback(addChange, remote, changei)
+    return d
+
+#----------------------------------------------------------------------
+def connected(remote):
+    return addChange(None, remote, changes.__iter__())
+
+#----------------------------------------------------------------------
+def cleanup(res):
+    reactor.stop()
+
diff --git a/xfbuildservice/contants.py b/xfbuildservice/contants.py
new file mode 100644
index 0000000..619bc4b
--- /dev/null
+++ b/xfbuildservice/contants.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+
+
+XF_RESULT_OK = 0
+XF_RESULT_INVALID_INPUT = 1
+
+result_codes = {
+    XF_RESULT_OK: 'Success',
+    XF_RESULT_INVALID_INPUT: 'Invalid input'
+}
diff --git a/xfbuildservice/doc.txt b/xfbuildservice/doc.txt
new file mode 100644
index 0000000..8a3a1f6
--- /dev/null
+++ b/xfbuildservice/doc.txt
@@ -0,0 +1,87 @@
+Xfce Buildbot Task Distribution Service
+=======================================
+
+
+Rationale
+---------
+
+For some reasons (e.g Xfce uses multiple GIT repositories within one URL)
+we need to use separate buildbot instances for each module in the GIT
+repositories to get them working reliable and correctly.
+
+This change will require that the GIT hook scripts would connect to their
+corresponding buildbot in order to trigger a new build after a commit has
+been done. To avoid too heavy changes on the GIT hook scripts side, we
+decided to deploy a new service which collects the hook scripts triggers
+from the various modules and deliver them to the appropriate buildbot
+instances.
+
+This way the GIT hook scripts don't need to know anything about the
+buildbot infrastructure, so less action and logic is necessary. Furthermore,
+this ensures the only communication between the Xfce GIT server and
+the Buildbot server is between the GIT hook scripts and the service described
+here which will run on the buildbot server.
+
+The service receives calls the necessary information (module name,
+old revision, new revision, branch, ...) from the GIT hook scripts via XMLRPC.
+Then the received information are passed to the buildbot instance responsible
+for the corresponding module.
+
+
+
+Implementation
+--------------
+
+The service will be implemented as a XMLRPC service using Python 2.5.
+It will run on the Xfce Buildbot server (buildbot.xfce.org) and listen
+for incoming requests sent by the GIT hook scripts.
+To ensure only valid requests are accepted, only connections from the Xfce GIT
+server are accepted. Additionally, the communication will be encrypted via SSL.
+
+The received build requests are forwarded to the corresponding buildbot
+instances running on localhost (i.e. buildbot.xfce.org) to actually
+trigger the build. The corresponding buildbot instance is determined by the
+passed module name inside the request.
+
+
+Request data
+^^^^^^^^^^^^
+
+The following information are transmitted in the XMLRPC calls from the
+GIT hook scripts to the service:
+
+'module name' - (str) mandatory, e.g. "xfce4-panel"
+'revision'    - (str) mandatory (buildbot specific)
+'comments'    - (str) mandatory (buildbot specific)
+'branch'      - (str) mandatory (buildbot specific)
+'category'    - (str) optional (buildbot specific)
+'files'       - (str) optional (buildbot specific)
+'who'         - (str) optional (buildbot specific)
+
+The above fields are packed into a list of structures (like a Python dictionary or
+an associative array). For example::
+
+    [
+        {
+           'module_name' => 'thunar',
+           'revision' => 'ab12cd34',
+           'branch' => 'master',
+           'comments' => 'First commit message'
+        },
+        {
+           'module_name' => 'thunar',
+           'revision' => 'fe5678ab',
+           'branch' => 'master',
+           'comments' => 'Second commit message'
+        }
+        {
+           'module_name' => 'thunar',
+           'revision' => 'ba4712f2',
+           'branch' => 'master',
+           'comments' => 'Rewind branch'
+        }
+    ]
+
+The XMLRPC is called "request_build" and returns a structure containing
+two fields "result_code" (int) and "result_message" (string) describing
+whether the operation was successful or not.
diff --git a/xfbuildservice/xfbuildservice.conf b/xfbuildservice/xfbuildservice.conf
new file mode 100644
index 0000000..7cdf914
--- /dev/null
+++ b/xfbuildservice/xfbuildservice.conf
@@ -0,0 +1,6 @@
+# $Id: xnmapi.conf 1084 2009-10-05 12:34:39Z etroeger $
+
+[buildbot_modulename]
+host: localhost:9990
+user: username
+passwd: passwd
diff --git a/xfbuildservice/xfbuildservice.py b/xfbuildservice/xfbuildservice.py
new file mode 100644
index 0000000..563a700
--- /dev/null
+++ b/xfbuildservice/xfbuildservice.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+
+from SimpleXMLRPCServer import CGIXMLRPCRequestHandler
+from ConfigParser import SafeConfigParser
+from optparse import OptionParser
+import os
+
+from contants import *
+from buildbot_client import send_changes
+
+
+class XfBuildService(object):
+    #----------------------------------------------------------------------
+    def __init__(self, config):
+        self.config = config
+
+    #----------------------------------------------------------------------
+    def request_build(self, data):
+        """
+        XMLRPC call to receive data from the caller and pass it to the
+        appropriate buildbot instance
+
+        @param data (list)
+        """
+        if not data or not isinstance(data, list):
+            return get_result(XF_RESULT_INVALID_INPUT)
+
+        modules = {}
+        for change in data:
+            module = change['module_name']
+            del change['module_name']
+            try:
+                modules[module].append(change)
+            except KeyError:
+                # create item
+                modules[module] = [ change ]
+
+        for module, changes in modules.items():
+            section = 'buildbot_%s' % module
+            send_changes(
+                self.config.get(section, 'host'),
+                self.config.getint(section, 'port'),
+                self.config.get(section, 'user'),
+                self.config.get(section, 'passwd'),
+                changes
+            )
+
+        return { 'result_code': 0, 'result_message': 'success' }
+
+#----------------------------------------------------------------------
+def get_result(code):
+    """
+    Get result dictionary for the XMLRPC reply
+
+    @param result (dict)
+    """
+    return { 'result_code': code, 'result_message': result_codes[code] }
+
+#----------------------------------------------------------------------
+def setup_options(parser):
+    """
+    Set up options and defaults
+
+    @param parser (optparse.OptionParser())
+    """
+    parser.add_option(
+        "-c", dest="config",
+        default=os.path.join(os.path.dirname(__file__), 'xfbuildservice.conf'),
+        help=u"configuration file")
+
+#----------------------------------------------------------------------
+def main():
+    # arguments
+    option_parser = OptionParser()
+    setup_options(option_parser)
+    arg_options = option_parser.parse_args()[0]
+
+    # configuration
+    config = SafeConfigParser()
+    if not os.path.exists(arg_options.config):
+        raise RuntimeError(u'Configuration file does not exist')
+    config.read(arg_options.config)
+
+    # XMLRPC handler
+    xfbs = XfBuildService(config)
+    handler = CGIXMLRPCRequestHandler()
+    handler.register_instance(xfbs)
+    handler.register_introspection_functions()
+    handler.handle_request()
+
+
+main()
diff --git a/xfcebuildstatus.py b/xfcebuildstatus.py
index 29464ce..2f9c394 100644
--- a/xfcebuildstatus.py
+++ b/xfcebuildstatus.py
@@ -20,16 +20,16 @@
 #       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 
 
-from xmlrpclib import ServerProxy, Error
-from string import Template
+from cgi import FieldStorage, print_environ
+from Cookie import SimpleCookie
 from datetime import datetime
-import cgi
+from string import Template
+from xmlrpclib import ServerProxy, Error
 import os
 import pprint
 
 
-
-XMLRPC_URL = 'http://buildbot.xfce.org/core/xmlrpc'
+XMLRPC_URL = 'http://localhost:8020/xmlrpc'
 PLATFORM_NAMES = {
     'debian': 'Debian GNU/Linux 5.0.3 (x86_64)',
     'lunar': 'Lunar Linux (x86_64)'
@@ -40,19 +40,25 @@ template = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 
 <head>
-    <title>Xfce Buildbots Status</title>
+    <title>${title}</title>
     <meta http-equiv="content-type" content="text/html;charset=utf-8" />
     <meta name="generator" content="Geany 0.19" />
+    <link rel="stylesheet" media="screen" href="http://www.xfce.org/layout/css/layout.css" type="text/css" />
+    <link rel="stylesheet" media="screen" href="http://www.xfce.org/layout/css/front.css" type="text/css" />
     <style type="text/css">
-        body, p, pre, td {
-            font-size: 12px;
-            font-family: sans-serif;
+        ${layout_css}
+        /* link styles */
+        :link, :visited, :link:active, :link:active {
+          color: #154374;
+          text-decoration: underline;
         }
-        img {
-            border: 0px;
+        :link:hover, :visited:hover {
+          color: #000000;
+          text-decoration: underline;
         }
-        a {
+        .status_link {
             color: #000000;
+            text-decoration: none;
         }
         h1 {
             margin-top: 25px;
@@ -68,17 +74,13 @@ template = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
             text-align: right;
         }
         .status_success {
-            background-color: #00ff00;
+            background-color: #8cff8c;
         }
         .status_failed {
-            background-color: #ff0000;
+            background-color: #ff7171;
         }
         .status_unknown {
-            background-color: #ffff00;
-        }
-        .source {
-            padding-top: 15px;
-            font-size: 11px;
+            background-color: #ffff84;
         }
         .item {
             font-weight: bold;
@@ -90,14 +92,144 @@ template = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     </style>
 </head>
 
-<body>
-${content}
+<body id="top">
+    <div class="hidden">
+        <a href="#global-contentwrap" title="Skip site navigation" accesskey="1">Skip site navigation</a> (1)
+        <h1>Xfce Desktop Environment</h1>
+        <h4>...and everything goes faster!</h4>
+        <hr />
+    </div>
+    <div id="global-pagewrap">
+        <div id="global-page">
+            <div id="header">
+                <div id="header-logo"></div>
+                <div id="header-right">
+                    <div id="header-style">
+                    <h2 class="hidden">Website Layout</h2>
+                    <p>Layout: <a href="?layout=normal" title="Normal layout (Min: 740px, Max: 1000px)">Normal</a> /
+                       <a href="?layout=liquid" title="Fluid Layout (100% Width)">Liquid</a></p>
+                    </div>
+                </div>
+                <div id="header-menu">
+                    <ul>
+                        <li><a href="${url}">Overview</a></li>
+                    </ul>
+                </div>
+                <div id="header-white">
+                    <div id="header-language">
+                        <!-- TODO content? -->
+                    </div>
+                </div>
+            </div>
+        <h3>${title}</h3>
+        ${content}
+    </div>
+</div>
 </body>
-</html>"""
+</html>
+"""
 
 
 debug_out = ''
 
+
+class HtmlGen(object):
+    """Very simple HTML generator"""
+    # TODO add_element is nice, but for nested elements this still needs to be improved
+
+    def __init__(self, url, layout):
+        self.html = ''
+        self.title = ''
+        self.url = url
+        if layout == 'liquid':
+            self.layout = '#global-page { width: 100%; }'
+        else:
+            self.layout = '#global-page { min-width: 760px; max-width: 1000px; width: 100%; }'
+
+    #----------------------------------------------------------------------
+    def __nonzero__(self):
+        return self.html != ''
+
+    #----------------------------------------------------------------------
+    def __str__(self):
+        return Template(template).substitute(
+            url=self.url,
+            layout_css=self.layout,
+            title=self.title,
+            content=self.html)
+
+    #----------------------------------------------------------------------
+    def add_element(self, tagname, cdata=None, attribs={}):
+        """
+        Generic function to generate a complete HTML tag element named tagname with the contents cdata.
+        If cdata is None, a single tag is created (e.g. <br /> or <hr />). If cdata is an empty string,
+        a normal tag is created with no contents (e.g. <td></td>).
+        attribs is a dictionary of tag attributes.
+
+        @param tagname (str)
+        @param cdata (str)
+        @param attribs (dict)
+        """
+        if not tagname:
+            return
+
+        if cdata == None:
+            attribs_str = ''
+            for attr, value in attribs.items():
+                attribs_str += ' %s="%s"' % (attr, value)
+            self.html += '<%s%s />\n' % (tagname, attribs_str)
+        else:
+            self.open_tag(tagname, attribs=attribs)
+            self.add_cdata(cdata)
+            self.close_tag(tagname)
+
+    #----------------------------------------------------------------------
+    def open_tag(self, tagname, attribs={}):
+        """
+        Generic function to generate a HTML tag named tagname.
+        attribs is a dictionary of tag attributes.
+
+        @param tagname (str)
+        @param attribs (dict)
+        """
+        if not tagname:
+            return
+
+        attribs_str = ''
+        for attr, value in attribs.items():
+            attribs_str += ' %s="%s"' % (attr, value)
+        self.html += '<%s%s>' % (tagname, attribs_str)
+
+    #----------------------------------------------------------------------
+    def close_tag(self, tagname):
+        """
+        Close the given tag
+
+        @param tagname (str)
+        """
+        if tagname:
+            self.html += '</%s>' % (tagname)
+
+    #----------------------------------------------------------------------
+    def add_cdata(self, cdata):
+        """
+        Simply add the passed text to the HTML content
+
+        @param cdata (str)
+        """
+        if cdata:
+            self.html += cdata
+
+    #----------------------------------------------------------------------
+    def set_title(self, title):
+        """
+        Set the document title
+
+        @param title (str)
+        """
+        self.title = title
+
+
 #----------------------------------------------------------------------
 def split_builder_name(input):
     """
@@ -162,18 +294,21 @@ def list_to_str(input):
     return steps[0:-2]
 
 #----------------------------------------------------------------------
-def get_build_status_html(url, input):
+def get_build_status_html(html, url, input):
     """
     Create HTML code from the given input.
 
+    @param html (HtmlGen)
     @param url (str)
     @param input (dict)
-    @return html code (str)
     """
-    result = '<table><tr>\n<td class="modules"></td>\n'
+    html.set_title('Xfce Buildbot Status')
+    html.open_tag('table')
+    html.open_tag('tr')
+    html.add_element('td', 'Module')
     for name in PLATFORM_NAMES.values():
-        result += '<td>%s</td>\n' % name
-    result += '</tr>\n'
+        html.add_element('td', name)
+    html.close_tag('tr')
 
     sorted_builders = []
     for x in input.items():
@@ -181,36 +316,39 @@ def get_build_status_html(url, input):
     sorted_builders.sort(cmp=lambda x,y: cmp(x[0], y[0]))
 
     for module_name, status in sorted_builders:
-        result += '<tr>\n<td class="modules">%s</td>\n' % module_name
+        html.open_tag('tr')
+        html.add_element('td', module_name)
         for name in PLATFORM_NAMES:
             try:
                 if status[name][0] == 'success':
-                    result += '''<td class="status_success"><a href="%s/detail/s/%s/%d"
-                                title="Built revision %s on %s">%s</a></td>\n''' % \
-                        (url, status[name][6], status[name][3], status[name][2], status[name][1], status[name][0])
+                    link_url = '%s/detail/%s' % (url, status[name][6])
+                    title = 'Built revision %s on %s' % (status[name][2][:7], status[name][1])
+                    html.open_tag('td', attribs={'class': 'status_success'})
+                    html.add_element('a', status[name][0], attribs={'href':link_url, 'title':title, 'class': 'status_link'})
+                    html.close_tag('td')
                 elif status[name][0] == 'failure':
                     steps = list_to_str(status[name][4])
                     reasons = list_to_str(status[name][5])
-                    #~steps = status[name][4]
-                    #~reasons = ''
                     desc = '%s failed to build at %s (on %s, reason: %s)' % \
                         (module_name, steps, status[name][1], reasons)
                     if status[name][2]:
-                        desc += ' Revision %s' % (status[name][2])
-                    result += '<td class="status_failed"><a href="%s/detail/f/%s/%d" title="%s">%s</a></td>\n' % \
-                        (url, status[name][6], status[name][3], desc, status[name][0])
+                        desc += ' Revision %s' % (status[name][2][:7])
+                    link_url = '%s/detail/%s' % (url, status[name][6])
+
+                    html.open_tag('td', {'class': 'status_failed'})
+                    html.add_element('a', status[name][0], attribs={'href':link_url, 'title':desc, 'class': 'status_link'})
+                    html.close_tag('td')
                 else:
-                    result += '<td class="status_unknown">unknown</td>\n'
+                    # trigger the exception handler below
+                    raise KeyError
             except KeyError:
-                result += '<td class="status_unknown">unknown</td>\n'
-        result += '</tr>\n'
-
-    result += '</table>\n<p class="source"><a href="%s/source">Script Source</a></p>' % url
+                html.add_element('td', 'unknown', attribs={'class': 'status_unknown'})
 
-    return Template(template).substitute(content=result)
+        html.close_tag('tr')
+    html.close_tag('table')
 
 #----------------------------------------------------------------------
-def get_detail_status(url, args):
+def get_detail_status(url, module):
     """
     Query the buildbot at url for the detailed build status as specified in args
 
@@ -219,13 +357,10 @@ def get_detail_status(url, args):
     @return status per module (dict)
     """
     global debug_out
-    server = ServerProxy(url)
 
+    server = ServerProxy(url)
     try:
-        if args[1] == 's':
-            builder = server.getBuild(args[2], int(args[3]))
-        else:
-            builder = server.getBuild(args[2], -1)
+        builder = server.getBuild(module, -1)
     except Exception, e:
         return { }
 
@@ -270,13 +405,13 @@ def format_time(duration):
     return ", ".join(eta_parts)
 
 #----------------------------------------------------------------------
-def get_detail_status_html(url, input):
+def get_detail_status_html(html, url, input):
     """
     Create HTML code from the given input.
 
+    @param html (HtmlGen)
     @param url (str)
     @param input (dict)
-    @return html code (str)
     """
     result = ''
     if input:
@@ -292,15 +427,47 @@ def get_detail_status_html(url, input):
         status_class = 'status_success' if status == 'successful' else 'status_failed'
 
         # output
-        result += '<h1>Build status for "%s"</h1>\n' % module_name
-        result += '<p><span class="item">Build result:</span> <span class="%s">%s</span></p>\n' % (status_class, status)
-        result += '<p><span class="item">Build started:</span> %s</p>\n' % start
-        result += '<p><span class="item">Build finished:</span> %s</p>\n' % end
-        result += '<p><span class="item">Build duration:</span> %s</p>\n' % duration
-        result += '<p><span class="item">Build reason:</span> %s</p>\n' % input['reason']
-        result += '<p><span class="item">Built revision:</span> %s</p>\n' % input['revision']
-        result += '<p><span class="item">Build steps:</span></p>'
-        result += '<p>\n'
+        html.set_title('Build Status for "%s"' % module_name)
+
+        html.add_element('p', ' ')
+        html.open_tag('p')
+        html.add_element('span', 'Build result:', attribs={'class':'item'})
+        html.add_cdata(' ')
+        html.add_element('span', status, attribs={'class':status_class})
+        html.close_tag('p')
+        html.open_tag('p')
+        html.add_element('span', 'Build started:', attribs={'class':'item'})
+        html.add_cdata(' %s' % start)
+        html.close_tag('p')
+        html.open_tag('p')
+        html.add_element('span', 'Build finished:', attribs={'class':'item'})
+        html.add_cdata(' %s' % end)
+        html.close_tag('p')
+        html.open_tag('p')
+        html.add_element('span', 'Build duration:', attribs={'class':'item'})
+        html.add_cdata(' %s' % duration)
+        html.close_tag('p')
+        html.open_tag('p')
+        html.add_element('span', 'Build reason:', attribs={'class':'item'})
+        html.add_cdata(' %s' % input['reason'])
+        html.close_tag('p')
+        try:
+            platform_str = PLATFORM_NAMES[platform]
+            html.add_element('span', 'Build platform:', attribs={'class':'item'})
+            html.add_cdata(' %s' % platform_str)
+            html.close_tag('p')
+        except KeyError:
+            pass
+        html.open_tag('p')
+        html.add_element('span', 'Build revision:', attribs={'class':'item'})
+        html.add_cdata(' %s' % input['revision'])
+        html.close_tag('p')
+        html.add_element('span', 'Build number:', attribs={'class':'item'})
+        html.add_cdata(' %s' % input['number'])
+        html.close_tag('p')
+        html.add_element('p', 'Build steps:', attribs={'class':'item'})
+
+        html.open_tag('p')
         for step in input['steps']:
             # make Samuel happy :)
             if step['name'].startswith('chmod '):
@@ -308,18 +475,27 @@ def get_detail_status_html(url, input):
             log_suffix = find_log(input['logs'], step['name'])
             log_url = ''
             if log_suffix:
-                log_url = ', <a href="%s/steps/%s/logs/%s">Logs</a>' % (input['url'], log_suffix[0], log_suffix[1])
-            result += '<span class="step"><span class="item">%s: %s</span> (%s%s)</span><br/>\n' % \
-                (step['name'], ' '.join(step['text']), format_time(step['end'] - step['start']), log_url)
-        result += '</p>\n'
-        result += '<p> </p>'
-        result += '<p><span class="item">More details:</span> <a href="%s">%s</a></p>\n' % (input['url'], input['url'])
+                log_url = '%s/steps/%s/logs/%s' % (input['url'], log_suffix[0], log_suffix[1])
+                html.open_tag('span', attribs={'class':'step'})
+                html.add_element('span', '%s: %s' % (step['name'], ' '.join(step['text'])), attribs={'class':'item'})
+                html.add_cdata(' (%s ' % format_time(step['end'] - step['start']))
+                html.add_element('a', 'Logs', attribs={'href':log_url})
+                html.add_cdata(')')
+                html.close_tag('span')
+                html.add_element('br')
+
+        html.close_tag('p')
+        html.add_element('p', ' ')
+        html.open_tag('p')
+        html.add_element('span', 'More details:', attribs={'class':'item'})
+        html.add_cdata(' ')
+        html.add_element('a', input['url'], attribs={'href':input['url']})
+        html.close_tag('p')
 
     else:
-        # do something here
-        pass
-
-    return Template(template).substitute(content=result)
+        # TODO do something here
+        html.set_title('Xfce Build Status error')
+        html.add_element('p', 'Currently no information can be retrieved for failed builds :(')
 
 #----------------------------------------------------------------------
 def parse_url():
@@ -329,60 +505,96 @@ def parse_url():
     @return url, args (tuple)
     """
     try:
+        uri = os.environ['REQUEST_URI']
         me = os.path.basename(os.environ['SCRIPT_FILENAME'])
         if me[0] != '/':
             me = '/%s' % me
-        arg_pos = os.environ['REQUEST_URI'].find(me)
+        arg_pos = uri.find(me)
         if arg_pos >= 0:
-            args = os.environ['REQUEST_URI'][(arg_pos+len(me)+1):].split('/')
+            args = uri[(arg_pos+len(me)+1):].split('/')
         else:
-            me = '/'
-            args = url
+            me = ''
+            args = uri.split('/')
+            if not args[0]:
+                del args[0]
         return (me, args)
     except:
         return ('', '')
 
 #----------------------------------------------------------------------
+def get_layout(fs, env, headers):
+    """
+    Try to read the desired layout (normal or liquid) from the query string (?layout=normal).
+    If found, set the passed layout as Cookie to store it permanently.
+    Otherwise try to read the value from a Cookie.
+    Finally, use 'normal' as default.
+
+    @param fs (cgi.FieldStorage)
+    @param env (dict)
+    @param headers (list)
+    @return layout (str)
+    """
+    # default
+    layout = 'normal'
+    # read/set layout cookie
+    if fs.has_key('layout') :
+        layout = fs['layout'].value
+        layout_cookie = SimpleCookie()
+        layout_cookie['layout'] = layout
+        # 4 weeks
+        layout_cookie['layout']['max-age'] = 2592000
+        layout_cookie['layout']['path'] = '/'
+        headers.append(layout_cookie)
+    else:
+        try:
+            layout_cookie = SimpleCookie()
+            layout_cookie.load(os.environ['HTTP_COOKIE'])
+            layout = layout_cookie['layout'].value
+        except KeyError:
+            pass
+
+    # prevent malicious user input by manipulated cookies
+    if layout != 'liquid':
+        layout = 'normal'
+
+    return layout
+
+#----------------------------------------------------------------------
 def main():
-    debug = False
-    if os.environ['REMOTE_ADDR'] in ['127.0.0.1', '62.75.200.254']:
-        # debug :)
+    headers = [ 'Content-type: text/html' ]
+
+    # parse query string
+    fs = FieldStorage(keep_blank_values=True)
+    debug = fs.has_key('debug')
+    if debug:
         import cgitb
         cgitb.enable(display=1)
-        debug = True
+    layout = get_layout(fs, os.environ, headers)
 
-    # handle arguments
+    # parse arguments
     url, args = parse_url()
 
-    output = ''
-    headers = []
-    if args and args[0]:
-        if args[0] == 'source':
-            headers.append('Content-Disposition: attachment; filename=%s' % url)
-            headers.append('Content-type: text/x-python')
-            fp = open(__file__, 'r')
-            output = fp.read()
-            fp.close()
-        elif args[0] == 'detail' and len(args) == 4:
-            status = get_detail_status(XMLRPC_URL, args)
-            output = get_detail_status_html(url, status)
+    # handle arguments, do the action, generate HTML
+    html = HtmlGen(url, layout)
+    if args:
+        args_len = len(args)
+        if args[0] == 'detail' and args_len == 2:
+            status = get_detail_status(XMLRPC_URL, args[1])
+            get_detail_status_html(html, url, status)
 
     # fallback to overview page
-    if not output:
+    if not html:
         status = get_build_status(XMLRPC_URL)
-        output = get_build_status_html(url, status)
+        get_build_status_html(html, url, status)
 
-    if not headers:
-        print 'Content-type: text/html'
-    else:
-        for h in headers:
-            print h
+    for h in headers:
+        print h
     print # final blank line to end HTTP headers
 
-    print output
-    if debug and debug_out:
+    print str(html)
+    if debug:
         print debug_out
+        print print_environ()
 
 main()
 
-



More information about the Xfce4-commits mailing list