~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/controllers/__init__.py

  • Committer: Michael Hudson
  • Date: 2007-12-12 08:36:51 UTC
  • mto: This revision was merged to the branch mainline in revision 143.
  • Revision ID: michael.hudson@canonical.com-20071212083651-oyh34swjxbzceyi1
add a comment for spiv

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#
2
 
# Copyright (C) 2008  Canonical Ltd.
3
2
# Copyright (C) 2006  Robey Pointer <robey@lag.net>
4
3
# Copyright (C) 2006  Goffredo Baroncelli <kreijack@inwind.it>
5
4
#
16
15
# You should have received a copy of the GNU General Public License
17
16
# along with this program; if not, write to the Free Software
18
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
#
19
19
 
20
 
import bzrlib.errors
21
 
import simplejson
 
20
import logging
 
21
import os
 
22
import re
 
23
import sys
22
24
import time
23
25
 
24
 
from paste.httpexceptions import HTTPNotFound, HTTPSeeOther
25
 
from paste.request import path_info_pop, parse_querystring
 
26
import turbogears
 
27
from turbogears import controllers
 
28
from cherrypy import NotFound
 
29
from configobj import ConfigObj
26
30
 
27
31
from loggerhead import util
28
 
from loggerhead.templatefunctions import templatefunctions
29
 
from loggerhead.zptsupport import load_template
30
 
 
31
 
 
32
 
class BufferingWriter(object):
33
 
 
34
 
    def __init__(self, writefunc, buf_limit):
35
 
        self.bytes = 0
36
 
        self.buf = []
37
 
        self.buflen = 0
38
 
        self.writefunc = writefunc
39
 
        self.buf_limit = buf_limit
40
 
 
41
 
    def flush(self):
42
 
        self.writefunc(''.join(self.buf))
43
 
        self.buf = []
44
 
        self.buflen = 0
45
 
 
46
 
    def write(self, data):
47
 
        self.buf.append(data)
48
 
        self.buflen += len(data)
49
 
        self.bytes += len(data)
50
 
        if self.buflen > self.buf_limit:
51
 
            self.flush()
52
 
 
53
 
 
54
 
class TemplatedBranchView(object):
55
 
 
56
 
    template_path = None
57
 
    supports_json = False
58
 
 
59
 
    def __init__(self, branch, history_callable):
60
 
        self._branch = branch
61
 
        self._history_callable = history_callable
62
 
        self.__history = None
63
 
        self.log = branch.log
64
 
 
65
 
    @property
66
 
    def _history(self):
67
 
        if self.__history is not None:
68
 
            return self.__history
69
 
        self.__history = self._history_callable()
70
 
        return self.__history
71
 
 
72
 
    def parse_args(self, environ):
73
 
        kwargs = dict(parse_querystring(environ))
74
 
        util.set_context(kwargs)
75
 
        args = []
76
 
        while True:
77
 
            arg = path_info_pop(environ)
78
 
            if arg is None:
79
 
                break
80
 
            args.append(arg)
81
 
 
82
 
        path = None
83
 
        if len(args) > 1:
84
 
            path = unicode('/'.join(args[1:]), 'utf-8')
85
 
        self.args = args
86
 
        self.kwargs = kwargs
87
 
        return path
88
 
 
89
 
    def add_template_values(self, values):
90
 
        values.update({
91
 
            'static_url': self._branch.static_url,
92
 
            'branch': self._branch,
 
32
from loggerhead.branchview import BranchView
 
33
from loggerhead.history import History, is_branch
 
34
 
 
35
log = logging.getLogger("loggerhead.controllers")
 
36
 
 
37
 
 
38
def cherrypy_friendly(s):
 
39
    """
 
40
    convert a config section name into a name that pleases cherrypy.
 
41
    """
 
42
    return re.sub(r'[^\w\d_]', '_', s)
 
43
 
 
44
 
 
45
class Project (object):
 
46
    def __init__(self, name, config):
 
47
        self.name = name
 
48
        self.friendly_name = config.get('name', name)
 
49
        self.description = config.get('description', '')
 
50
        self.long_description = config.get('long_description', '')
 
51
        self._config = config
 
52
        
 
53
        self._views = []
 
54
        for view_name in config.sections:
 
55
            log.debug('Configuring (project %s) branch %s...', name, view_name)
 
56
            self._add_view(view_name, config[view_name], config[view_name].get('folder'))
 
57
        
 
58
        self._auto_folder = config.get('auto_publish_folder', None)
 
59
        self._auto_list = []
 
60
        if self._auto_folder is not None:
 
61
            self._recheck_auto_folders()
 
62
    
 
63
    def _recheck_auto_folders(self):
 
64
        if self._auto_folder is None:
 
65
            return
 
66
        auto_list = []
 
67
        # scan a folder for bazaar branches, and add them automatically
 
68
        for path, folders, filenames in os.walk(self._auto_folder):
 
69
            for folder in folders:
 
70
                folder = os.path.join(path, folder)
 
71
                if is_branch(folder):
 
72
                    auto_list.append(folder)
 
73
        auto_list.sort()
 
74
        if auto_list == self._auto_list:
 
75
            # nothing has changed; do nothing.
 
76
            return
 
77
 
 
78
        # rebuild views:
 
79
        log.debug('Rescanning auto-folder for project %s ...', self.name)
 
80
        self._views = []
 
81
        for folder in auto_list:
 
82
            view_name = os.path.basename(folder)
 
83
            log.debug('Auto-configuring (project %s) branch %s...', self.name, view_name)
 
84
            self._add_view(view_name, ConfigObj(), folder)
 
85
        self._auto_list = auto_list
 
86
        
 
87
    def _add_view(self, view_name, view_config, folder):
 
88
        c_view_name = cherrypy_friendly(view_name)
 
89
        view = BranchView(self.name, c_view_name, view_name, folder, view_config, self._config)
 
90
        self._views.append(view)
 
91
        setattr(self, c_view_name, view)
 
92
        
 
93
    views = property(lambda self: self._views)
 
94
 
 
95
 
 
96
class Root (controllers.RootController):
 
97
    def __init__(self, config):
 
98
        self._projects = []
 
99
        self._config = config
 
100
        for project_name in self._config.sections:
 
101
            c_project_name = cherrypy_friendly(project_name)
 
102
            project = Project(c_project_name, self._config[project_name])
 
103
            self._projects.append(project)
 
104
            setattr(self, c_project_name, project)
 
105
        
 
106
    @turbogears.expose(template='loggerhead.templates.browse')
 
107
    def index(self):
 
108
        for p in self._projects:
 
109
            p._recheck_auto_folders()
 
110
        return {
 
111
            'projects': self._projects,
93
112
            'util': util,
94
 
            'url': self._branch.context_url,
95
 
        })
96
 
        values.update(templatefunctions)
97
 
 
98
 
    def __call__(self, environ, start_response):
99
 
        z = time.time()
100
 
        if environ.get('loggerhead.as_json') and not self.supports_json:
101
 
            raise HTTPNotFound
102
 
        path = self.parse_args(environ)
103
 
        headers = {}
104
 
        values = self.get_values(path, self.kwargs, headers)
105
 
 
106
 
        self.log.info('Getting information for %s: %.3f secs' % (
107
 
            self.__class__.__name__, time.time() - z))
108
 
        if environ.get('loggerhead.as_json'):
109
 
            headers['Content-Type'] = 'application/json'
110
 
        elif 'Content-Type' not in headers:
111
 
            headers['Content-Type'] = 'text/html'
112
 
        writer = start_response("200 OK", headers.items())
113
 
        if environ.get('REQUEST_METHOD') == 'HEAD':
114
 
            # No content for a HEAD request
115
 
            return []
116
 
        z = time.time()
117
 
        w = BufferingWriter(writer, 8192)
118
 
        if environ.get('loggerhead.as_json'):
119
 
            w.write(simplejson.dumps(values,
120
 
                default=util.convert_to_json_ready))
121
 
        else:
122
 
            self.add_template_values(values)
123
 
            template = load_template(self.template_path)
124
 
            template.expand_into(w, **values)
125
 
        w.flush()
126
 
        self.log.info(
127
 
            'Rendering %s: %.3f secs, %s bytes' % (
128
 
                self.__class__.__name__, time.time() - z, w.bytes))
129
 
        return []
130
 
 
131
 
    def get_revid(self):
132
 
        h = self._history
133
 
        if h is None:
134
 
            return None
135
 
        if len(self.args) > 0 and self.args != ['']:
136
 
            try:
137
 
                revid = h.fix_revid(self.args[0])
138
 
            except bzrlib.errors.NoSuchRevision:
139
 
                raise HTTPNotFound;
140
 
        else:
141
 
            revid = h.last_revid
142
 
        return revid
 
113
            'title': self._config.get('title', ''),
 
114
        }
 
115
 
 
116
    def _check_rebuild(self):
 
117
        for p in self._projects:
 
118
            for v in p.views:
 
119
                v.check_rebuild()
 
120
 
 
121
 
 
122
 
 
123
# for use in profiling the very-slow get_change() method:
 
124
#h = Root.bazaar.bzr_dev.get_history()
 
125
#w = list(h.get_revision_history())
 
126
#h._get_changes_profiled(w[:100])
 
127