~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/apps/config.py

  • Committer: Matt Nordhoff
  • Date: 2009-05-06 18:40:43 UTC
  • mfrom: (334.2.8 more-news)
  • Revision ID: mnordhoff@mattnordhoff.com-20090506184043-o1aglwdowl19a8rm
Improve NEWS for the upcoming release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""A server that uses a loggerhead.conf file.
 
2
 
 
3
We recreate the branch discovery and url scheme of the old branchview
 
4
code.  It's all a bit horrible really.
 
5
"""
 
6
 
 
7
import logging
 
8
import os
 
9
import posixpath
 
10
 
 
11
import bzrlib.lru_cache
 
12
 
 
13
from bzrlib.util.configobj.configobj import ConfigObj
 
14
 
 
15
from paste.request import path_info_pop
 
16
from paste import httpexceptions
 
17
from paste.wsgiwrappers import WSGIResponse
 
18
 
 
19
from loggerhead.apps.branch import BranchWSGIApp
 
20
from loggerhead.apps import favicon_app, static_app, robots_app
 
21
from loggerhead.templatefunctions import templatefunctions
 
22
from loggerhead.zptsupport import load_template
 
23
from loggerhead import util
 
24
 
 
25
log = logging.getLogger("loggerhead.controllers")
 
26
 
 
27
from loggerhead.history import is_branch
 
28
 
 
29
 
 
30
class Project(object):
 
31
    """A project contains the branches.
 
32
 
 
33
    There is some complication because we don't want to hold on to the
 
34
    branches when they are not being browsed."""
 
35
 
 
36
    def __init__(self, name, config, root_config, graph_cache):
 
37
        self.name = name
 
38
        self.friendly_name = config.get('name', name)
 
39
        self.description = config.get('description', '')
 
40
        self.long_description = config.get('long_description', '')
 
41
        self._config = config
 
42
        self._root_config = root_config
 
43
        self.graph_cache = graph_cache
 
44
 
 
45
        self.view_names = []
 
46
        self.view_data_by_name = {}
 
47
        for view_name in config.sections:
 
48
            log.debug('Configuring (project %s) branch %s...', name, view_name)
 
49
            self._add_view(
 
50
                view_name, config[view_name], config[view_name].get('folder'))
 
51
 
 
52
        self._auto_folder = config.get('auto_publish_folder', None)
 
53
        self._auto_list = []
 
54
        if self._auto_folder is not None:
 
55
            self._recheck_auto_folders()
 
56
 
 
57
    def _recheck_auto_folders(self):
 
58
        if self._auto_folder is None:
 
59
            return
 
60
        auto_list = []
 
61
        # scan a folder for bazaar branches, and add them automatically
 
62
        for path, folders, filenames in os.walk(self._auto_folder):
 
63
            for folder in folders:
 
64
                folder = os.path.join(path, folder)
 
65
                if is_branch(folder):
 
66
                    auto_list.append(folder)
 
67
        auto_list.sort()
 
68
        if auto_list == self._auto_list:
 
69
            # nothing has changed; do nothing.
 
70
            return
 
71
 
 
72
        # rebuild views:
 
73
        self.view_names = []
 
74
        log.debug('Rescanning auto-folder for project %s ...', self.name)
 
75
        for folder in auto_list:
 
76
            view_name = os.path.basename(folder)
 
77
            log.debug('Auto-configuring (project %s) branch %s...',
 
78
                      self.name,
 
79
                      view_name)
 
80
            self._add_view(view_name, ConfigObj(), folder)
 
81
        self._auto_list = auto_list
 
82
 
 
83
    def _get_branch_url(self, view, view_config, folder):
 
84
        url = view_config.get('url', None)
 
85
        if url is not None:
 
86
            return url
 
87
        url = self._config.get('url_prefix', None)
 
88
        if url is not None:
 
89
            return posixpath.join(url, folder) + '/'
 
90
        return None
 
91
 
 
92
    def _get_description(self, view, view_config, history):
 
93
        description = view_config.get('description', None)
 
94
        if description is not None:
 
95
            return description
 
96
        description = history._branch.get_config().get_user_option(
 
97
                          'description')
 
98
        return description
 
99
 
 
100
    def _add_view(self, view_name, view_config, folder):
 
101
        b = bzrlib.branch.Branch.open(folder)
 
102
        view = BranchWSGIApp(b, view_name, view_config, self.graph_cache)
 
103
        b.lock_read()
 
104
        try:
 
105
            history = view.get_history()
 
106
            friendly_name = view_config.get('branch_name', None)
 
107
            if friendly_name is None:
 
108
                friendly_name = history.get_config().get_nickname()
 
109
                if friendly_name is None:
 
110
                    friendly_name = view_name
 
111
            self.view_data_by_name[view_name] = {
 
112
                'branch_path': folder,
 
113
                'args': (view_name, view_config, self.graph_cache),
 
114
                'description': self._get_description(view,
 
115
                                                     view_config,
 
116
                                                     history),
 
117
                '_src_folder': folder,
 
118
                '_view_config': view_config,
 
119
                'friendly_name': friendly_name,
 
120
                'name': view_name,
 
121
                }
 
122
            branch_url = self._get_branch_url(view, view_config, view_name)
 
123
            if branch_url is not None:
 
124
                self.view_data_by_name[view_name]['branch_url'] = branch_url
 
125
            self.view_names.append(view_name)
 
126
        finally:
 
127
            b.unlock()
 
128
 
 
129
    def view_named(self, name):
 
130
        view_data = self.view_data_by_name.get(name)
 
131
        if view_data is None:
 
132
            return None
 
133
        view_data = view_data.copy()
 
134
        branch_path = view_data.pop('branch_path')
 
135
        args = view_data.pop('args')
 
136
        b = bzrlib.branch.Branch.open(branch_path)
 
137
        b.lock_read()
 
138
        view = BranchWSGIApp(b, *args)
 
139
        for k in view_data:
 
140
            setattr(view, k, view_data[k])
 
141
        return view
 
142
 
 
143
    def call(self, environ, start_response):
 
144
        segment = path_info_pop(environ)
 
145
        if not segment:
 
146
            raise httpexceptions.HTTPNotFound()
 
147
        else:
 
148
            view = self.view_named(segment)
 
149
            if view is None:
 
150
                raise httpexceptions.HTTPNotFound()
 
151
            try:
 
152
                return view.app(environ, start_response)
 
153
            finally:
 
154
                view.branch.unlock()
 
155
 
 
156
 
 
157
class Root(object):
 
158
    """The root of the server -- renders as the browse view,
 
159
    dispatches to Project above for each 'project'."""
 
160
 
 
161
    def __init__(self, config):
 
162
        self.projects = []
 
163
        self.config = config
 
164
        self.projects_by_name = {}
 
165
        graph_cache = bzrlib.lru_cache.LRUCache()
 
166
        for project_name in self.config.sections:
 
167
            project = Project(project_name, self.config[project_name],
 
168
                              self.config, graph_cache)
 
169
            self.projects.append(project)
 
170
            self.projects_by_name[project_name] = project
 
171
 
 
172
    def browse(self, response):
 
173
        # This is insanely complicated because we want to open and
 
174
        # lock all the branches, render the view and then unlock the
 
175
        # branches again.
 
176
        for p in self.projects:
 
177
            p._recheck_auto_folders()
 
178
 
 
179
        class branch(object):
 
180
 
 
181
            @staticmethod
 
182
            def static_url(path):
 
183
                return self._static_url_base + path
 
184
        views_by_project = {}
 
185
        all_views = []
 
186
        try:
 
187
            for p in self.projects:
 
188
                views_by_project[p] = []
 
189
                for vn in p.view_names:
 
190
                    v = p.view_named(vn)
 
191
                    all_views.append(v)
 
192
                    views_by_project[p].append(v)
 
193
            vals = {
 
194
                'projects': self.projects,
 
195
                'util': util,
 
196
                'title': self.config.get('title', None),
 
197
                'branch': branch,
 
198
                'views_by_project': views_by_project,
 
199
            }
 
200
            vals.update(templatefunctions)
 
201
            response.headers['Content-Type'] = 'text/html'
 
202
            template = load_template('loggerhead.templates.browse')
 
203
            template.expand_into(response, **vals)
 
204
        finally:
 
205
            for v in all_views:
 
206
                v.branch.unlock()
 
207
 
 
208
    def __call__(self, environ, start_response):
 
209
        self._static_url_base = environ['loggerhead.static.url'] = \
 
210
                                environ['SCRIPT_NAME']
 
211
        segment = path_info_pop(environ)
 
212
        if segment is None:
 
213
            raise httpexceptions.HTTPMovedPermanently(
 
214
                environ['SCRIPT_NAME'] + '/')
 
215
        elif segment == '':
 
216
            response = WSGIResponse()
 
217
            self.browse(response)
 
218
            return response(environ, start_response)
 
219
        elif segment == 'robots.txt':
 
220
            return robots_app(environ, start_response)
 
221
        elif segment == 'static':
 
222
            return static_app(environ, start_response)
 
223
        elif segment == 'favicon.ico':
 
224
            return favicon_app(environ, start_response)
 
225
        else:
 
226
            project = self.projects_by_name.get(segment)
 
227
            if project is None:
 
228
                raise httpexceptions.HTTPNotFound()
 
229
            return project.call(environ, start_response)