~jelmer/loggerhead/breezy-compat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# Copyright (C) 2008-2011 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335  USA

"""The WSGI application for serving a Bazaar branch."""

import logging
import sys
import wsgiref.util

import breezy.branch
import breezy.errors
from breezy.hooks import Hooks
import breezy.lru_cache
from breezy.sixish import viewitems
from breezy import urlutils

from paste import request
from paste import httpexceptions

from ..apps import static_app
from ..controllers.annotate_ui import AnnotateUI
from ..controllers.view_ui import ViewUI
from ..controllers.atom_ui import AtomUI
from ..controllers.changelog_ui import ChangeLogUI
from ..controllers.diff_ui import DiffUI
from ..controllers.download_ui import DownloadUI, DownloadTarballUI
from ..controllers.filediff_ui import FileDiffUI
from ..controllers.inventory_ui import InventoryUI
from ..controllers.revision_ui import RevisionUI
from ..controllers.revlog_ui import RevLogUI
from ..controllers.search_ui import SearchUI
from ..history import History
from .. import util


_DEFAULT = object()

class BranchWSGIApp(object):

    def __init__(self, branch, friendly_name=None, config={},
                 graph_cache=None, branch_link=None, is_root=False,
                 served_url=_DEFAULT, use_cdn=False, private=False,
                 export_tarballs=True):
        """Create branch-publishing WSGI app.

        :param export_tarballs: If true, allow downloading snapshots of revisions
            as tarballs.
        """
        self.branch = branch
        self._config = config
        self.friendly_name = friendly_name
        self.branch_link = branch_link  # Currently only used in Launchpad
        self.log = logging.getLogger('loggerhead.%s' % (friendly_name,))
        if graph_cache is None:
            graph_cache = breezy.lru_cache.LRUCache(10)
        self.graph_cache = graph_cache
        self.is_root = is_root
        self.served_url = served_url
        self.use_cdn = use_cdn
        self.private = private
        self.export_tarballs = export_tarballs

    def public_private_css(self):
        if self.private:
            return "private"
        else:
            return "public"

    def get_history(self):
        revinfo_disk_cache = None
        cache_path = self._config.get('cachepath', None)
        if cache_path is not None:
            # Only import the cache if we're going to use it.
            # This makes sqlite optional
            try:
                from ..changecache import RevInfoDiskCache
            except ImportError:
                self.log.debug("Couldn't load python-sqlite,"
                               " continuing without using a cache")
            else:
                revinfo_disk_cache = RevInfoDiskCache(cache_path)
        return History(
            self.branch, self.graph_cache,
            revinfo_disk_cache=revinfo_disk_cache,
            cache_key=(self.friendly_name.encode('utf-8') if self.friendly_name else None))

    # Before the addition of this method, clicking to sort by date from 
    # within a branch caused a jump up to the top of that branch.
    def sort_url(self, *args, **kw):
        if isinstance(args[0], list):
            args = args[0]
        qs = []
        for k, v in viewitems(kw):
            if v is not None:
                qs.append('%s=%s' % (k, urlutils.quote(v)))
        qs = '&'.join(qs)
        path_info = self._path_info.strip('/').split('?')[0]
        path_info += '?' + qs
        return self._url_base + '/' + path_info

    def url(self, *args, **kw):
        if isinstance(args[0], list):
            args = args[0]
        qs = []
        for k, v in viewitems(kw):
            if v is not None:
                qs.append('%s=%s' % (k, urlutils.quote(v)))
        qs = '&'.join(qs)
        path_info = urlutils.quote('/'.join(args), safe='/~:')
        if qs:
            path_info += '?' + qs
        return self._url_base + path_info

    def absolute_url(self, *args, **kw):
        rel_url = self.url(*args, **kw)
        return request.resolve_relative_url(rel_url, self._environ)

    def context_url(self, *args, **kw):
        kw = util.get_context(**kw)
        return self.url(*args, **kw)

    def static_url(self, path):
        return self._static_url_base + path

    def yui_url(self, path):
        if self.use_cdn:
            base = 'http://yui.yahooapis.com/3.0.0pr2/build/'
        else:
            base = self.static_url('/static/javascript/yui/build/')
        return base + path

    controllers_dict = {
        '+filediff': FileDiffUI,
        '+revlog': RevLogUI,
        'annotate': AnnotateUI,
        'atom': AtomUI,
        'changes': ChangeLogUI,
        'diff': DiffUI,
        'download': DownloadUI,
        'files': InventoryUI,
        'revision': RevisionUI,
        'search': SearchUI,
        'view': ViewUI,
        'tarball': DownloadTarballUI,
        }

    def last_updated(self):
        h = self.get_history()
        change = h.get_changes([h.last_revid])[0]
        return change.date

    def public_branch_url(self):
        return self.branch.get_public_branch()

    def lookup_app(self, environ):
        # Check again if the branch is blocked from being served, this is
        # mostly for tests. It's already checked in apps/transport.py
        if not self.branch.get_config().get_user_option_as_bool('http_serve', default=True):
            raise httpexceptions.HTTPNotFound()
        self._url_base = environ['SCRIPT_NAME']
        self._path_info = environ['PATH_INFO']
        self._static_url_base = environ.get('loggerhead.static.url')
        if self._static_url_base is None:
            self._static_url_base = self._url_base
        self._environ = environ
        if self.served_url is _DEFAULT:
            public_branch = self.public_branch_url()
            if public_branch is not None:
                self.served_url = public_branch
            else:
                self.served_url = wsgiref.util.application_uri(environ)
        for hook in self.hooks['controller']:
            controller = hook(self, environ)
            if controller is not None:
                return controller
        path = request.path_info_pop(environ)
        if not path:
            raise httpexceptions.HTTPMovedPermanently(
                self.absolute_url('/changes'))
        if path == 'static':
            return static_app
        elif path == '+json':
            environ['loggerhead.as_json'] = True
            path = request.path_info_pop(environ)
        cls = self.controllers_dict.get(path)
        if cls is not None:
            return cls(self, self.get_history)
        raise httpexceptions.HTTPNotFound()

    def app(self, environ, start_response):
        with self.branch.lock_read():
            try:
                c = self.lookup_app(environ)
                return c(environ, start_response)
            except:
                environ['exc_info'] = sys.exc_info()
                environ['branch'] = self
                raise


class BranchWSGIAppHooks(Hooks):
    """A dictionary mapping hook name to a list of callables for WSGI app branch hooks.
    """

    def __init__(self):
        """Create the default hooks.
        """
        Hooks.__init__(self, "breezy.plugins.loggerhead.apps.branch",
            "BranchWSGIApp.hooks")
        self.add_hook('controller',
            "Invoked when looking for the controller to use for a "
            "branch subpage. The api signature is (branch_app, environ)."
            "If a hook can provide a controller, it should return one, "
            "as a standard WSGI app. If it can't provide a controller, "
            "it should return None", (1, 19))


BranchWSGIApp.hooks = BranchWSGIAppHooks()