/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/smart/request.py

  • Committer: John Arbash Meinel
  • Date: 2008-07-09 21:42:24 UTC
  • mto: This revision was merged to the branch mainline in revision 3543.
  • Revision ID: john@arbash-meinel.com-20080709214224-r75k87r6a01pfc3h
Restore a real weave merge to 'bzr merge --weave'.

To do so efficiently, we only add the simple LCAs to the final weave
object, unless we run into complexities with the merge graph.
This gives the same effective result as adding all the texts,
with the advantage of not having to extract all of them.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 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
 
 
17
"""Basic server-side logic for dealing with requests.
 
18
 
 
19
**XXX**:
 
20
 
 
21
The class names are a little confusing: the protocol will instantiate a
 
22
SmartServerRequestHandler, whose dispatch_command method creates an instance of
 
23
a SmartServerRequest subclass.
 
24
 
 
25
The request_handlers registry tracks SmartServerRequest classes (rather than
 
26
SmartServerRequestHandler).
 
27
"""
 
28
 
 
29
import tempfile
 
30
 
 
31
from bzrlib import (
 
32
    bzrdir,
 
33
    errors,
 
34
    registry,
 
35
    revision,
 
36
    urlutils,
 
37
    )
 
38
from bzrlib.bundle.serializer import write_bundle
 
39
 
 
40
 
 
41
class SmartServerRequest(object):
 
42
    """Base class for request handlers.
 
43
    
 
44
    To define a new request, subclass this class and override the `do` method
 
45
    (and if appropriate, `do_body` as well).  Request implementors should take
 
46
    care to call `translate_client_path` and `transport_from_client_path` as
 
47
    appropriate when dealing with paths received from the client.
 
48
    """
 
49
    # XXX: rename this class to BaseSmartServerRequestHandler ?  A request
 
50
    # *handler* is a different concept to the request.
 
51
 
 
52
    def __init__(self, backing_transport, root_client_path='/'):
 
53
        """Constructor.
 
54
 
 
55
        :param backing_transport: the base transport to be used when performing
 
56
            this request.
 
57
        :param root_client_path: the client path that maps to the root of
 
58
            backing_transport.  This is used to interpret relpaths received
 
59
            from the client.  Clients will not be able to refer to paths above
 
60
            this root.  If root_client_path is None, then no translation will
 
61
            be performed on client paths.  Default is '/'.
 
62
        """
 
63
        self._backing_transport = backing_transport
 
64
        if root_client_path is not None:
 
65
            if not root_client_path.startswith('/'):
 
66
                root_client_path = '/' + root_client_path
 
67
            if not root_client_path.endswith('/'):
 
68
                root_client_path += '/'
 
69
        self._root_client_path = root_client_path
 
70
 
 
71
    def _check_enabled(self):
 
72
        """Raises DisabledMethod if this method is disabled."""
 
73
        pass
 
74
 
 
75
    def do(self, *args):
 
76
        """Mandatory extension point for SmartServerRequest subclasses.
 
77
        
 
78
        Subclasses must implement this.
 
79
        
 
80
        This should return a SmartServerResponse if this command expects to
 
81
        receive no body.
 
82
        """
 
83
        raise NotImplementedError(self.do)
 
84
 
 
85
    def execute(self, *args):
 
86
        """Public entry point to execute this request.
 
87
 
 
88
        It will return a SmartServerResponse if the command does not expect a
 
89
        body.
 
90
 
 
91
        :param *args: the arguments of the request.
 
92
        """
 
93
        self._check_enabled()
 
94
        return self.do(*args)
 
95
 
 
96
    def do_body(self, body_bytes):
 
97
        """Called if the client sends a body with the request.
 
98
 
 
99
        The do() method is still called, and must have returned None.
 
100
        
 
101
        Must return a SmartServerResponse.
 
102
        """
 
103
        raise NotImplementedError(self.do_body)
 
104
 
 
105
    def do_chunk(self, chunk_bytes):
 
106
        """Called with each body chunk if the request has a streamed body.
 
107
 
 
108
        The do() method is still called, and must have returned None.
 
109
        """
 
110
        raise NotImplementedError(self.do_chunk)
 
111
 
 
112
    def do_end(self):
 
113
        """Called when the end of the request has been received."""
 
114
        pass
 
115
    
 
116
    def translate_client_path(self, client_path):
 
117
        """Translate a path received from a network client into a local
 
118
        relpath.
 
119
 
 
120
        All paths received from the client *must* be translated.
 
121
 
 
122
        :param client_path: the path from the client.
 
123
        :returns: a relpath that may be used with self._backing_transport
 
124
            (unlike the untranslated client_path, which must not be used with
 
125
            the backing transport).
 
126
        """
 
127
        if self._root_client_path is None:
 
128
            # no translation necessary!
 
129
            return client_path
 
130
        if not client_path.startswith('/'):
 
131
            client_path = '/' + client_path
 
132
        if client_path.startswith(self._root_client_path):
 
133
            path = client_path[len(self._root_client_path):]
 
134
            relpath = urlutils.joinpath('/', path)
 
135
            if not relpath.startswith('/'):
 
136
                raise ValueError(relpath)
 
137
            return '.' + relpath
 
138
        else:
 
139
            raise errors.PathNotChild(client_path, self._root_client_path)
 
140
 
 
141
    def transport_from_client_path(self, client_path):
 
142
        """Get a backing transport corresponding to the location referred to by
 
143
        a network client.
 
144
 
 
145
        :seealso: translate_client_path
 
146
        :returns: a transport cloned from self._backing_transport
 
147
        """
 
148
        relpath = self.translate_client_path(client_path)
 
149
        return self._backing_transport.clone(relpath)
 
150
 
 
151
 
 
152
class SmartServerResponse(object):
 
153
    """A response to a client request.
 
154
    
 
155
    This base class should not be used. Instead use
 
156
    SuccessfulSmartServerResponse and FailedSmartServerResponse as appropriate.
 
157
    """
 
158
 
 
159
    def __init__(self, args, body=None, body_stream=None):
 
160
        """Constructor.
 
161
 
 
162
        :param args: tuple of response arguments.
 
163
        :param body: string of a response body.
 
164
        :param body_stream: iterable of bytestrings to be streamed to the
 
165
            client.
 
166
        """
 
167
        self.args = args
 
168
        if body is not None and body_stream is not None:
 
169
            raise errors.BzrError(
 
170
                "'body' and 'body_stream' are mutually exclusive.")
 
171
        self.body = body
 
172
        self.body_stream = body_stream
 
173
 
 
174
    def __eq__(self, other):
 
175
        if other is None:
 
176
            return False
 
177
        return (other.args == self.args and
 
178
                other.body == self.body and
 
179
                other.body_stream is self.body_stream)
 
180
 
 
181
    def __repr__(self):
 
182
        status = {True: 'OK', False: 'ERR'}[self.is_successful()]
 
183
        return "<SmartServerResponse status=%s args=%r body=%r>" % (status,
 
184
            self.args, self.body)
 
185
 
 
186
 
 
187
class FailedSmartServerResponse(SmartServerResponse):
 
188
    """A SmartServerResponse for a request which failed."""
 
189
 
 
190
    def is_successful(self):
 
191
        """FailedSmartServerResponse are not successful."""
 
192
        return False
 
193
 
 
194
 
 
195
class SuccessfulSmartServerResponse(SmartServerResponse):
 
196
    """A SmartServerResponse for a successfully completed request."""
 
197
 
 
198
    def is_successful(self):
 
199
        """SuccessfulSmartServerResponse are successful."""
 
200
        return True
 
201
 
 
202
 
 
203
class SmartServerRequestHandler(object):
 
204
    """Protocol logic for smart server.
 
205
    
 
206
    This doesn't handle serialization at all, it just processes requests and
 
207
    creates responses.
 
208
    """
 
209
 
 
210
    # IMPORTANT FOR IMPLEMENTORS: It is important that SmartServerRequestHandler
 
211
    # not contain encoding or decoding logic to allow the wire protocol to vary
 
212
    # from the object protocol: we will want to tweak the wire protocol separate
 
213
    # from the object model, and ideally we will be able to do that without
 
214
    # having a SmartServerRequestHandler subclass for each wire protocol, rather
 
215
    # just a Protocol subclass.
 
216
 
 
217
    # TODO: Better way of representing the body for commands that take it,
 
218
    # and allow it to be streamed into the server.
 
219
 
 
220
    def __init__(self, backing_transport, commands, root_client_path):
 
221
        """Constructor.
 
222
 
 
223
        :param backing_transport: a Transport to handle requests for.
 
224
        :param commands: a registry mapping command names to SmartServerRequest
 
225
            subclasses. e.g. bzrlib.transport.smart.vfs.vfs_commands.
 
226
        """
 
227
        self._backing_transport = backing_transport
 
228
        self._root_client_path = root_client_path
 
229
        self._commands = commands
 
230
        self._body_bytes = ''
 
231
        self.response = None
 
232
        self.finished_reading = False
 
233
        self._command = None
 
234
 
 
235
    def accept_body(self, bytes):
 
236
        """Accept body data."""
 
237
 
 
238
        # TODO: This should be overriden for each command that desired body data
 
239
        # to handle the right format of that data, i.e. plain bytes, a bundle,
 
240
        # etc.  The deserialisation into that format should be done in the
 
241
        # Protocol object.
 
242
 
 
243
        # default fallback is to accumulate bytes.
 
244
        self._body_bytes += bytes
 
245
        
 
246
    def end_of_body(self):
 
247
        """No more body data will be received."""
 
248
        self._run_handler_code(self._command.do_body, (self._body_bytes,), {})
 
249
        # cannot read after this.
 
250
        self.finished_reading = True
 
251
 
 
252
    def dispatch_command(self, cmd, args):
 
253
        """Deprecated compatibility method.""" # XXX XXX
 
254
        try:
 
255
            command = self._commands.get(cmd)
 
256
        except LookupError:
 
257
            raise errors.UnknownSmartMethod(cmd)
 
258
        self._command = command(self._backing_transport, self._root_client_path)
 
259
        self._run_handler_code(self._command.execute, args, {})
 
260
 
 
261
    def _run_handler_code(self, callable, args, kwargs):
 
262
        """Run some handler specific code 'callable'.
 
263
 
 
264
        If a result is returned, it is considered to be the commands response,
 
265
        and finished_reading is set true, and its assigned to self.response.
 
266
 
 
267
        Any exceptions caught are translated and a response object created
 
268
        from them.
 
269
        """
 
270
        result = self._call_converting_errors(callable, args, kwargs)
 
271
 
 
272
        if result is not None:
 
273
            self.response = result
 
274
            self.finished_reading = True
 
275
 
 
276
    def _call_converting_errors(self, callable, args, kwargs):
 
277
        """Call callable converting errors to Response objects."""
 
278
        # XXX: most of this error conversion is VFS-related, and thus ought to
 
279
        # be in SmartServerVFSRequestHandler somewhere.
 
280
        try:
 
281
            return callable(*args, **kwargs)
 
282
        except errors.NoSuchFile, e:
 
283
            return FailedSmartServerResponse(('NoSuchFile', e.path))
 
284
        except errors.FileExists, e:
 
285
            return FailedSmartServerResponse(('FileExists', e.path))
 
286
        except errors.DirectoryNotEmpty, e:
 
287
            return FailedSmartServerResponse(('DirectoryNotEmpty', e.path))
 
288
        except errors.ShortReadvError, e:
 
289
            return FailedSmartServerResponse(('ShortReadvError',
 
290
                e.path, str(e.offset), str(e.length), str(e.actual)))
 
291
        except UnicodeError, e:
 
292
            # If it is a DecodeError, than most likely we are starting
 
293
            # with a plain string
 
294
            str_or_unicode = e.object
 
295
            if isinstance(str_or_unicode, unicode):
 
296
                # XXX: UTF-8 might have \x01 (our seperator byte) in it.  We
 
297
                # should escape it somehow.
 
298
                val = 'u:' + str_or_unicode.encode('utf-8')
 
299
            else:
 
300
                val = 's:' + str_or_unicode.encode('base64')
 
301
            # This handles UnicodeEncodeError or UnicodeDecodeError
 
302
            return FailedSmartServerResponse((e.__class__.__name__,
 
303
                    e.encoding, val, str(e.start), str(e.end), e.reason))
 
304
        except errors.TransportNotPossible, e:
 
305
            if e.msg == "readonly transport":
 
306
                return FailedSmartServerResponse(('ReadOnlyError', ))
 
307
            else:
 
308
                raise
 
309
 
 
310
    def headers_received(self, headers):
 
311
        # Just a no-op at the moment.
 
312
        pass
 
313
 
 
314
    def args_received(self, args):
 
315
        cmd = args[0]
 
316
        args = args[1:]
 
317
        try:
 
318
            command = self._commands.get(cmd)
 
319
        except LookupError:
 
320
            raise errors.UnknownSmartMethod(cmd)
 
321
        self._command = command(self._backing_transport)
 
322
        self._run_handler_code(self._command.execute, args, {})
 
323
 
 
324
    def prefixed_body_received(self, body_bytes):
 
325
        """No more body data will be received."""
 
326
        self._run_handler_code(self._command.do_body, (body_bytes,), {})
 
327
        # cannot read after this.
 
328
        self.finished_reading = True
 
329
 
 
330
    def body_chunk_received(self, chunk_bytes):
 
331
        self._run_handler_code(self._command.do_chunk, (chunk_bytes,), {})
 
332
 
 
333
    def end_received(self):
 
334
        self._run_handler_code(self._command.do_end, (), {})
 
335
 
 
336
 
 
337
class HelloRequest(SmartServerRequest):
 
338
    """Answer a version request with the highest protocol version this server
 
339
    supports.
 
340
    """
 
341
 
 
342
    def do(self):
 
343
        return SuccessfulSmartServerResponse(('ok', '2'))
 
344
 
 
345
 
 
346
class GetBundleRequest(SmartServerRequest):
 
347
    """Get a bundle of from the null revision to the specified revision."""
 
348
 
 
349
    def do(self, path, revision_id):
 
350
        # open transport relative to our base
 
351
        t = self.transport_from_client_path(path)
 
352
        control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
 
353
        repo = control.open_repository()
 
354
        tmpf = tempfile.TemporaryFile()
 
355
        base_revision = revision.NULL_REVISION
 
356
        write_bundle(repo, revision_id, base_revision, tmpf)
 
357
        tmpf.seek(0)
 
358
        return SuccessfulSmartServerResponse((), tmpf.read())
 
359
 
 
360
 
 
361
class SmartServerIsReadonly(SmartServerRequest):
 
362
    # XXX: this request method belongs somewhere else.
 
363
 
 
364
    def do(self):
 
365
        if self._backing_transport.is_readonly():
 
366
            answer = 'yes'
 
367
        else:
 
368
            answer = 'no'
 
369
        return SuccessfulSmartServerResponse((answer,))
 
370
 
 
371
 
 
372
request_handlers = registry.Registry()
 
373
request_handlers.register_lazy(
 
374
    'append', 'bzrlib.smart.vfs', 'AppendRequest')
 
375
request_handlers.register_lazy(
 
376
    'Branch.get_config_file', 'bzrlib.smart.branch', 'SmartServerBranchGetConfigFile')
 
377
request_handlers.register_lazy(
 
378
    'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')
 
379
request_handlers.register_lazy(
 
380
    'Branch.lock_write', 'bzrlib.smart.branch', 'SmartServerBranchRequestLockWrite')
 
381
request_handlers.register_lazy(
 
382
    'Branch.revision_history', 'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
 
383
request_handlers.register_lazy(
 
384
    'Branch.set_last_revision', 'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
 
385
request_handlers.register_lazy(
 
386
    'Branch.set_last_revision_info', 'bzrlib.smart.branch',
 
387
    'SmartServerBranchRequestSetLastRevisionInfo')
 
388
request_handlers.register_lazy(
 
389
    'Branch.set_last_revision_ex', 'bzrlib.smart.branch',
 
390
    'SmartServerBranchRequestSetLastRevisionEx')
 
391
request_handlers.register_lazy(
 
392
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
 
393
request_handlers.register_lazy(
 
394
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV1')
 
395
request_handlers.register_lazy(
 
396
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV2')
 
397
request_handlers.register_lazy(
 
398
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir', 'SmartServerRequestInitializeBzrDir')
 
399
request_handlers.register_lazy(
 
400
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBranch')
 
401
request_handlers.register_lazy(
 
402
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
 
403
request_handlers.register_lazy(
 
404
    'get', 'bzrlib.smart.vfs', 'GetRequest')
 
405
request_handlers.register_lazy(
 
406
    'get_bundle', 'bzrlib.smart.request', 'GetBundleRequest')
 
407
request_handlers.register_lazy(
 
408
    'has', 'bzrlib.smart.vfs', 'HasRequest')
 
409
request_handlers.register_lazy(
 
410
    'hello', 'bzrlib.smart.request', 'HelloRequest')
 
411
request_handlers.register_lazy(
 
412
    'iter_files_recursive', 'bzrlib.smart.vfs', 'IterFilesRecursiveRequest')
 
413
request_handlers.register_lazy(
 
414
    'list_dir', 'bzrlib.smart.vfs', 'ListDirRequest')
 
415
request_handlers.register_lazy(
 
416
    'mkdir', 'bzrlib.smart.vfs', 'MkdirRequest')
 
417
request_handlers.register_lazy(
 
418
    'move', 'bzrlib.smart.vfs', 'MoveRequest')
 
419
request_handlers.register_lazy(
 
420
    'put', 'bzrlib.smart.vfs', 'PutRequest')
 
421
request_handlers.register_lazy(
 
422
    'put_non_atomic', 'bzrlib.smart.vfs', 'PutNonAtomicRequest')
 
423
request_handlers.register_lazy(
 
424
    'readv', 'bzrlib.smart.vfs', 'ReadvRequest')
 
425
request_handlers.register_lazy(
 
426
    'rename', 'bzrlib.smart.vfs', 'RenameRequest')
 
427
request_handlers.register_lazy('Repository.gather_stats',
 
428
                               'bzrlib.smart.repository',
 
429
                               'SmartServerRepositoryGatherStats')
 
430
request_handlers.register_lazy('Repository.get_parent_map',
 
431
                               'bzrlib.smart.repository',
 
432
                               'SmartServerRepositoryGetParentMap')
 
433
request_handlers.register_lazy(
 
434
    'Repository.stream_knit_data_for_revisions',
 
435
    'bzrlib.smart.repository',
 
436
    'SmartServerRepositoryStreamKnitDataForRevisions')
 
437
request_handlers.register_lazy(
 
438
    'Repository.stream_revisions_chunked',
 
439
    'bzrlib.smart.repository',
 
440
    'SmartServerRepositoryStreamRevisionsChunked')
 
441
request_handlers.register_lazy(
 
442
    'Repository.get_revision_graph', 'bzrlib.smart.repository', 'SmartServerRepositoryGetRevisionGraph')
 
443
request_handlers.register_lazy(
 
444
    'Repository.has_revision', 'bzrlib.smart.repository', 'SmartServerRequestHasRevision')
 
445
request_handlers.register_lazy(
 
446
    'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
 
447
request_handlers.register_lazy(
 
448
    'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')
 
449
request_handlers.register_lazy(
 
450
    'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
 
451
request_handlers.register_lazy(
 
452
    'Repository.tarball', 'bzrlib.smart.repository',
 
453
    'SmartServerRepositoryTarball')
 
454
request_handlers.register_lazy(
 
455
    'rmdir', 'bzrlib.smart.vfs', 'RmdirRequest')
 
456
request_handlers.register_lazy(
 
457
    'stat', 'bzrlib.smart.vfs', 'StatRequest')
 
458
request_handlers.register_lazy(
 
459
    'Transport.is_readonly', 'bzrlib.smart.request', 'SmartServerIsReadonly')
 
460
request_handlers.register_lazy(
 
461
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')