~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/apps/branch.py

  • Committer: Robert Collins
  • Date: 2012-02-02 04:58:46 UTC
  • mfrom: (407.2.9 loggerhead)
  • mto: This revision was merged to the branch mainline in revision 465.
  • Revision ID: robertc@robertcollins.net-20120202045846-i221bwch22os8faq
Fix conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008-2011 Canonical Ltd.
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
1
17
"""The WSGI application for serving a Bazaar branch."""
2
18
 
3
19
import logging
5
21
import sys
6
22
 
7
23
import bzrlib.branch
 
24
import bzrlib.errors
 
25
from bzrlib.hooks import Hooks
8
26
import bzrlib.lru_cache
9
27
 
10
28
from paste import request
12
30
 
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
31
50
 
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.
 
56
 
 
57
        :param export_tarballs: If true, allow downloading snapshots of revisions
 
58
            as tarballs.
 
59
        """
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
 
73
 
 
74
    def public_private_css(self):
 
75
        if self.private:
 
76
            return "private"
 
77
        else:
 
78
            return "public"
46
79
 
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
53
86
            try:
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")
58
91
            else:
59
 
                _history.use_file_cache(
60
 
                    FileChangeCache(_history, cache_path))
61
 
        return _history
 
92
                revinfo_disk_cache = RevInfoDiskCache(cache_path)
 
93
        return History(
 
94
            self.branch, self.graph_cache,
 
95
            revinfo_disk_cache=revinfo_disk_cache, cache_key=self.friendly_name)
62
96
 
63
97
    def url(self, *args, **kw):
64
98
        if isinstance(args[0], list):
66
100
        qs = []
67
101
        for k, v in kw.iteritems():
68
102
            if v is not None:
69
 
                qs.append('%s=%s'%(k, urllib.quote(v)))
 
103
                qs.append('%s=%s' % (k, urllib.quote(v)))
70
104
        qs = '&'.join(qs)
71
 
        return request.construct_url(
72
 
            self._environ, script_name=self._url_base,
73
 
            path_info=unicode('/'.join(args)).encode('utf-8'),
74
 
            querystring=qs)
 
105
        path_info = urllib.quote(
 
106
            unicode('/'.join(args)).encode('utf-8'), safe='/~:')
 
107
        if qs:
 
108
            path_info += '?' + qs
 
109
        return self._url_base + path_info
 
110
 
 
111
    def absolute_url(self, *args, **kw):
 
112
        rel_url = self.url(*args, **kw)
 
113
        return request.resolve_relative_url(rel_url, self._environ)
75
114
 
76
115
    def context_url(self, *args, **kw):
77
116
        kw = util.get_context(**kw)
98
137
        'files': InventoryUI,
99
138
        'revision': RevisionUI,
100
139
        'search': SearchUI,
 
140
        'view': ViewUI,
 
141
        'tarball': DownloadTarballUI,
101
142
        }
102
143
 
103
144
    def last_updated(self):
105
146
        change = h.get_changes([h.last_revid])[0]
106
147
        return change.date
107
148
 
108
 
    def branch_url(self):
 
149
    def public_branch_url(self):
109
150
        return self.branch.get_config().get_user_option('public_branch')
110
151
 
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
 
166
            else:
 
167
                # Loggerhead only supports serving .bzr/ on local branches, so
 
168
                # we shouldn't suggest something that won't work.
 
169
                try:
 
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:
 
177
                return controller
119
178
        path = request.path_info_pop(environ)
120
179
        if not path:
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)
 
183
            return static_app
 
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)
126
 
        if cls is None:
127
 
            raise httpexceptions.HTTPNotFound()
 
188
        if cls is not None:
 
189
            return cls(self, self.get_history)
 
190
        raise httpexceptions.HTTPNotFound()
 
191
 
 
192
    def app(self, environ, start_response):
128
193
        self.branch.lock_read()
129
194
        try:
130
195
            try:
131
 
                c = cls(self, self.get_history)
 
196
                c = self.lookup_app(environ)
132
197
                return c(environ, start_response)
133
198
            except:
134
199
                environ['exc_info'] = sys.exc_info()
136
201
                raise
137
202
        finally:
138
203
            self.branch.unlock()
 
204
 
 
205
 
 
206
class BranchWSGIAppHooks(Hooks):
 
207
    """A dictionary mapping hook name to a list of callables for WSGI app branch hooks.
 
208
    """
 
209
 
 
210
    def __init__(self):
 
211
        """Create the default hooks.
 
212
        """
 
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))
 
221
 
 
222
 
 
223
BranchWSGIApp.hooks = BranchWSGIAppHooks()