1
# Copyright (C) 2008-2011 Canonical Ltd.
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1
17
"""The WSGI application for serving a Bazaar branch."""
13
31
from loggerhead.apps import static_app
14
32
from loggerhead.controllers.annotate_ui import AnnotateUI
33
from loggerhead.controllers.view_ui import ViewUI
15
34
from loggerhead.controllers.atom_ui import AtomUI
16
35
from loggerhead.controllers.changelog_ui import ChangeLogUI
17
36
from loggerhead.controllers.diff_ui import DiffUI
18
from loggerhead.controllers.download_ui import DownloadUI
37
from loggerhead.controllers.download_ui import DownloadUI, DownloadTarballUI
19
38
from loggerhead.controllers.filediff_ui import FileDiffUI
20
39
from loggerhead.controllers.inventory_ui import InventoryUI
21
40
from loggerhead.controllers.revision_ui import RevisionUI
32
51
def __init__(self, branch, friendly_name=None, config={},
33
52
graph_cache=None, branch_link=None, is_root=False,
34
served_url=_DEFAULT, use_cdn=False):
53
served_url=_DEFAULT, use_cdn=False, private=False,
54
export_tarballs=True):
55
"""Create branch-publishing WSGI app.
57
:param export_tarballs: If true, allow downloading snapshots of revisions
35
60
self.branch = branch
36
61
self._config = config
37
62
self.friendly_name = friendly_name
38
63
self.branch_link = branch_link # Currently only used in Launchpad
39
self.log = logging.getLogger('loggerhead.%s' % friendly_name)
64
self.log = logging.getLogger('loggerhead.%s' % (friendly_name,))
40
65
if graph_cache is None:
41
graph_cache = bzrlib.lru_cache.LRUCache()
66
graph_cache = bzrlib.lru_cache.LRUCache(10)
42
67
self.graph_cache = graph_cache
43
68
self.is_root = is_root
44
69
self.served_url = served_url
45
70
self.use_cdn = use_cdn
71
self.private = private
72
self.export_tarballs = export_tarballs
74
def public_private_css(self):
47
80
def get_history(self):
48
_history = History(self.branch, self.graph_cache)
81
revinfo_disk_cache = None
49
82
cache_path = self._config.get('cachepath', None)
50
83
if cache_path is not None:
51
84
# Only import the cache if we're going to use it.
52
85
# This makes sqlite optional
54
from loggerhead.changecache import FileChangeCache
87
from loggerhead.changecache import RevInfoDiskCache
55
88
except ImportError:
56
89
self.log.debug("Couldn't load python-sqlite,"
57
90
" continuing without using a cache")
59
_history.use_file_cache(
60
FileChangeCache(_history, cache_path))
92
revinfo_disk_cache = RevInfoDiskCache(cache_path)
94
self.branch, self.graph_cache,
95
revinfo_disk_cache=revinfo_disk_cache, cache_key=self.friendly_name)
63
97
def url(self, *args, **kw):
64
98
if isinstance(args[0], list):
67
101
for k, v in kw.iteritems():
69
qs.append('%s=%s'%(k, urllib.quote(v)))
103
qs.append('%s=%s' % (k, urllib.quote(v)))
71
return request.construct_url(
72
self._environ, script_name=self._url_base,
73
path_info=unicode('/'.join(args)).encode('utf-8'),
105
path_info = urllib.quote(
106
unicode('/'.join(args)).encode('utf-8'), safe='/~:')
108
path_info += '?' + qs
109
return self._url_base + path_info
111
def absolute_url(self, *args, **kw):
112
rel_url = self.url(*args, **kw)
113
return request.resolve_relative_url(rel_url, self._environ)
76
115
def context_url(self, *args, **kw):
77
116
kw = util.get_context(**kw)
105
146
change = h.get_changes([h.last_revid])[0]
106
147
return change.date
108
def branch_url(self):
149
def public_branch_url(self):
109
150
return self.branch.get_config().get_user_option('public_branch')
111
def app(self, environ, start_response):
152
def lookup_app(self, environ):
153
# Check again if the branch is blocked from being served, this is
154
# mostly for tests. It's already checked in apps/transport.py
155
if self.branch.get_config().get_user_option('http_serve') == 'False':
156
raise httpexceptions.HTTPNotFound()
112
157
self._url_base = environ['SCRIPT_NAME']
113
158
self._static_url_base = environ.get('loggerhead.static.url')
114
159
if self._static_url_base is None:
115
160
self._static_url_base = self._url_base
116
161
self._environ = environ
117
162
if self.served_url is _DEFAULT:
118
self.served_url = self.url([])
163
public_branch = self.public_branch_url()
164
if public_branch is not None:
165
self.served_url = public_branch
167
# Loggerhead only supports serving .bzr/ on local branches, so
168
# we shouldn't suggest something that won't work.
170
util.local_path_from_url(self.branch.base)
171
self.served_url = self.url([])
172
except bzrlib.errors.InvalidURL:
173
self.served_url = None
174
for hook in self.hooks['controller']:
175
controller = hook(self, environ)
176
if controller is not None:
119
178
path = request.path_info_pop(environ)
121
180
raise httpexceptions.HTTPMovedPermanently(
122
self._url_base + '/changes')
181
self.absolute_url('/changes'))
123
182
if path == 'static':
124
return static_app(environ, start_response)
184
elif path == '+json':
185
environ['loggerhead.as_json'] = True
186
path = request.path_info_pop(environ)
125
187
cls = self.controllers_dict.get(path)
127
raise httpexceptions.HTTPNotFound()
189
return cls(self, self.get_history)
190
raise httpexceptions.HTTPNotFound()
192
def app(self, environ, start_response):
128
193
self.branch.lock_read()
131
c = cls(self, self.get_history)
196
c = self.lookup_app(environ)
132
197
return c(environ, start_response)
134
199
environ['exc_info'] = sys.exc_info()
138
203
self.branch.unlock()
206
class BranchWSGIAppHooks(Hooks):
207
"""A dictionary mapping hook name to a list of callables for WSGI app branch hooks.
211
"""Create the default hooks.
213
Hooks.__init__(self, "bzrlib.plugins.loggerhead.apps.branch",
214
"BranchWSGIApp.hooks")
215
self.add_hook('controller',
216
"Invoked when looking for the controller to use for a "
217
"branch subpage. The api signature is (branch_app, environ)."
218
"If a hook can provide a controller, it should return one, "
219
"as a standard WSGI app. If it can't provide a controller, "
220
"it should return None", (1, 19))
223
BranchWSGIApp.hooks = BranchWSGIAppHooks()