~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/apps/config.py

Merge maxb's update to jam's removal of start/stop loggerhead scripts.

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