~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/request.py

  • Committer: Robert Collins
  • Date: 2007-11-09 17:50:31 UTC
  • mto: This revision was merged to the branch mainline in revision 2988.
  • Revision ID: robertc@robertcollins.net-20071109175031-agaiy6530rvbprmb
Change (without backwards compatibility) the
iter_lines_added_or_present_in_versions VersionedFile API to yield the
text version that each line is being returned from. This is useful for
reconcile in determining what inventories reference what texts.
(Robert Collins)

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
 
 
20
import tempfile
 
21
 
 
22
from bzrlib import (
 
23
    bzrdir,
 
24
    errors,
 
25
    registry,
 
26
    revision,
 
27
    )
 
28
from bzrlib.bundle.serializer import write_bundle
 
29
 
 
30
 
 
31
class SmartServerRequest(object):
 
32
    """Base class for request handlers."""
 
33
 
 
34
    def __init__(self, backing_transport):
 
35
        """Constructor.
 
36
 
 
37
        :param backing_transport: the base transport to be used when performing
 
38
            this request.
 
39
        """
 
40
        self._backing_transport = backing_transport
 
41
 
 
42
    def _check_enabled(self):
 
43
        """Raises DisabledMethod if this method is disabled."""
 
44
        pass
 
45
 
 
46
    def do(self, *args):
 
47
        """Mandatory extension point for SmartServerRequest subclasses.
 
48
        
 
49
        Subclasses must implement this.
 
50
        
 
51
        This should return a SmartServerResponse if this command expects to
 
52
        receive no body.
 
53
        """
 
54
        raise NotImplementedError(self.do)
 
55
 
 
56
    def execute(self, *args):
 
57
        """Public entry point to execute this request.
 
58
 
 
59
        It will return a SmartServerResponse if the command does not expect a
 
60
        body.
 
61
 
 
62
        :param *args: the arguments of the request.
 
63
        """
 
64
        self._check_enabled()
 
65
        return self.do(*args)
 
66
 
 
67
    def do_body(self, body_bytes):
 
68
        """Called if the client sends a body with the request.
 
69
        
 
70
        Must return a SmartServerResponse.
 
71
        """
 
72
        # TODO: if a client erroneously sends a request that shouldn't have a
 
73
        # body, what to do?  Probably SmartServerRequestHandler should catch
 
74
        # this NotImplementedError and translate it into a 'bad request' error
 
75
        # to send to the client.
 
76
        raise NotImplementedError(self.do_body)
 
77
 
 
78
 
 
79
class SmartServerResponse(object):
 
80
    """A response to a client request.
 
81
    
 
82
    This base class should not be used. Instead use
 
83
    SuccessfulSmartServerResponse and FailedSmartServerResponse as appropriate.
 
84
    """
 
85
 
 
86
    def __init__(self, args, body=None):
 
87
        self.args = args
 
88
        self.body = body
 
89
 
 
90
    def __eq__(self, other):
 
91
        if other is None:
 
92
            return False
 
93
        return other.args == self.args and other.body == self.body
 
94
 
 
95
    def __repr__(self):
 
96
        return "<SmartServerResponse args=%r body=%r>" % (
 
97
            self.args, self.body)
 
98
 
 
99
 
 
100
class FailedSmartServerResponse(SmartServerResponse):
 
101
    """A SmartServerResponse for a request which failed."""
 
102
 
 
103
    def is_successful(self):
 
104
        """FailedSmartServerResponse are not successful."""
 
105
        return False
 
106
 
 
107
 
 
108
class SuccessfulSmartServerResponse(SmartServerResponse):
 
109
    """A SmartServerResponse for a successfully completed request."""
 
110
 
 
111
    def is_successful(self):
 
112
        """SuccessfulSmartServerResponse are successful."""
 
113
        return True
 
114
 
 
115
 
 
116
class SmartServerRequestHandler(object):
 
117
    """Protocol logic for smart server.
 
118
    
 
119
    This doesn't handle serialization at all, it just processes requests and
 
120
    creates responses.
 
121
    """
 
122
 
 
123
    # IMPORTANT FOR IMPLEMENTORS: It is important that SmartServerRequestHandler
 
124
    # not contain encoding or decoding logic to allow the wire protocol to vary
 
125
    # from the object protocol: we will want to tweak the wire protocol separate
 
126
    # from the object model, and ideally we will be able to do that without
 
127
    # having a SmartServerRequestHandler subclass for each wire protocol, rather
 
128
    # just a Protocol subclass.
 
129
 
 
130
    # TODO: Better way of representing the body for commands that take it,
 
131
    # and allow it to be streamed into the server.
 
132
 
 
133
    def __init__(self, backing_transport, commands):
 
134
        """Constructor.
 
135
 
 
136
        :param backing_transport: a Transport to handle requests for.
 
137
        :param commands: a registry mapping command names to SmartServerRequest
 
138
            subclasses. e.g. bzrlib.transport.smart.vfs.vfs_commands.
 
139
        """
 
140
        self._backing_transport = backing_transport
 
141
        self._commands = commands
 
142
        self._body_bytes = ''
 
143
        self.response = None
 
144
        self.finished_reading = False
 
145
        self._command = None
 
146
 
 
147
    def accept_body(self, bytes):
 
148
        """Accept body data."""
 
149
 
 
150
        # TODO: This should be overriden for each command that desired body data
 
151
        # to handle the right format of that data, i.e. plain bytes, a bundle,
 
152
        # etc.  The deserialisation into that format should be done in the
 
153
        # Protocol object.
 
154
 
 
155
        # default fallback is to accumulate bytes.
 
156
        self._body_bytes += bytes
 
157
        
 
158
    def end_of_body(self):
 
159
        """No more body data will be received."""
 
160
        self._run_handler_code(self._command.do_body, (self._body_bytes,), {})
 
161
        # cannot read after this.
 
162
        self.finished_reading = True
 
163
 
 
164
    def dispatch_command(self, cmd, args):
 
165
        """Deprecated compatibility method.""" # XXX XXX
 
166
        try:
 
167
            command = self._commands.get(cmd)
 
168
        except LookupError:
 
169
            raise errors.SmartProtocolError("bad request %r" % (cmd,))
 
170
        self._command = command(self._backing_transport)
 
171
        self._run_handler_code(self._command.execute, args, {})
 
172
 
 
173
    def _run_handler_code(self, callable, args, kwargs):
 
174
        """Run some handler specific code 'callable'.
 
175
 
 
176
        If a result is returned, it is considered to be the commands response,
 
177
        and finished_reading is set true, and its assigned to self.response.
 
178
 
 
179
        Any exceptions caught are translated and a response object created
 
180
        from them.
 
181
        """
 
182
        result = self._call_converting_errors(callable, args, kwargs)
 
183
 
 
184
        if result is not None:
 
185
            self.response = result
 
186
            self.finished_reading = True
 
187
 
 
188
    def _call_converting_errors(self, callable, args, kwargs):
 
189
        """Call callable converting errors to Response objects."""
 
190
        # XXX: most of this error conversion is VFS-related, and thus ought to
 
191
        # be in SmartServerVFSRequestHandler somewhere.
 
192
        try:
 
193
            return callable(*args, **kwargs)
 
194
        except errors.NoSuchFile, e:
 
195
            return FailedSmartServerResponse(('NoSuchFile', e.path))
 
196
        except errors.FileExists, e:
 
197
            return FailedSmartServerResponse(('FileExists', e.path))
 
198
        except errors.DirectoryNotEmpty, e:
 
199
            return FailedSmartServerResponse(('DirectoryNotEmpty', e.path))
 
200
        except errors.ShortReadvError, e:
 
201
            return FailedSmartServerResponse(('ShortReadvError',
 
202
                e.path, str(e.offset), str(e.length), str(e.actual)))
 
203
        except UnicodeError, e:
 
204
            # If it is a DecodeError, than most likely we are starting
 
205
            # with a plain string
 
206
            str_or_unicode = e.object
 
207
            if isinstance(str_or_unicode, unicode):
 
208
                # XXX: UTF-8 might have \x01 (our seperator byte) in it.  We
 
209
                # should escape it somehow.
 
210
                val = 'u:' + str_or_unicode.encode('utf-8')
 
211
            else:
 
212
                val = 's:' + str_or_unicode.encode('base64')
 
213
            # This handles UnicodeEncodeError or UnicodeDecodeError
 
214
            return FailedSmartServerResponse((e.__class__.__name__,
 
215
                    e.encoding, val, str(e.start), str(e.end), e.reason))
 
216
        except errors.TransportNotPossible, e:
 
217
            if e.msg == "readonly transport":
 
218
                return FailedSmartServerResponse(('ReadOnlyError', ))
 
219
            else:
 
220
                raise
 
221
 
 
222
 
 
223
class HelloRequest(SmartServerRequest):
 
224
    """Answer a version request with the highest protocol version this server
 
225
    supports.
 
226
    """
 
227
 
 
228
    def do(self):
 
229
        return SuccessfulSmartServerResponse(('ok', '2'))
 
230
 
 
231
 
 
232
class GetBundleRequest(SmartServerRequest):
 
233
    """Get a bundle of from the null revision to the specified revision."""
 
234
 
 
235
    def do(self, path, revision_id):
 
236
        # open transport relative to our base
 
237
        t = self._backing_transport.clone(path)
 
238
        control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
 
239
        repo = control.open_repository()
 
240
        tmpf = tempfile.TemporaryFile()
 
241
        base_revision = revision.NULL_REVISION
 
242
        write_bundle(repo, revision_id, base_revision, tmpf)
 
243
        tmpf.seek(0)
 
244
        return SuccessfulSmartServerResponse((), tmpf.read())
 
245
 
 
246
 
 
247
class SmartServerIsReadonly(SmartServerRequest):
 
248
    # XXX: this request method belongs somewhere else.
 
249
 
 
250
    def do(self):
 
251
        if self._backing_transport.is_readonly():
 
252
            answer = 'yes'
 
253
        else:
 
254
            answer = 'no'
 
255
        return SuccessfulSmartServerResponse((answer,))
 
256
 
 
257
 
 
258
request_handlers = registry.Registry()
 
259
request_handlers.register_lazy(
 
260
    'append', 'bzrlib.smart.vfs', 'AppendRequest')
 
261
request_handlers.register_lazy(
 
262
    'Branch.get_config_file', 'bzrlib.smart.branch', 'SmartServerBranchGetConfigFile')
 
263
request_handlers.register_lazy(
 
264
    'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')
 
265
request_handlers.register_lazy(
 
266
    'Branch.lock_write', 'bzrlib.smart.branch', 'SmartServerBranchRequestLockWrite')
 
267
request_handlers.register_lazy(
 
268
    'Branch.revision_history', 'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
 
269
request_handlers.register_lazy(
 
270
    'Branch.set_last_revision', 'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
 
271
request_handlers.register_lazy(
 
272
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
 
273
request_handlers.register_lazy(
 
274
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepository')
 
275
request_handlers.register_lazy(
 
276
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir', 'SmartServerRequestInitializeBzrDir')
 
277
request_handlers.register_lazy(
 
278
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBranch')
 
279
request_handlers.register_lazy(
 
280
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
 
281
request_handlers.register_lazy(
 
282
    'get', 'bzrlib.smart.vfs', 'GetRequest')
 
283
request_handlers.register_lazy(
 
284
    'get_bundle', 'bzrlib.smart.request', 'GetBundleRequest')
 
285
request_handlers.register_lazy(
 
286
    'has', 'bzrlib.smart.vfs', 'HasRequest')
 
287
request_handlers.register_lazy(
 
288
    'hello', 'bzrlib.smart.request', 'HelloRequest')
 
289
request_handlers.register_lazy(
 
290
    'iter_files_recursive', 'bzrlib.smart.vfs', 'IterFilesRecursiveRequest')
 
291
request_handlers.register_lazy(
 
292
    'list_dir', 'bzrlib.smart.vfs', 'ListDirRequest')
 
293
request_handlers.register_lazy(
 
294
    'mkdir', 'bzrlib.smart.vfs', 'MkdirRequest')
 
295
request_handlers.register_lazy(
 
296
    'move', 'bzrlib.smart.vfs', 'MoveRequest')
 
297
request_handlers.register_lazy(
 
298
    'put', 'bzrlib.smart.vfs', 'PutRequest')
 
299
request_handlers.register_lazy(
 
300
    'put_non_atomic', 'bzrlib.smart.vfs', 'PutNonAtomicRequest')
 
301
request_handlers.register_lazy(
 
302
    'readv', 'bzrlib.smart.vfs', 'ReadvRequest')
 
303
request_handlers.register_lazy(
 
304
    'rename', 'bzrlib.smart.vfs', 'RenameRequest')
 
305
request_handlers.register_lazy('Repository.gather_stats',
 
306
                               'bzrlib.smart.repository',
 
307
                               'SmartServerRepositoryGatherStats')
 
308
request_handlers.register_lazy(
 
309
    'Repository.stream_knit_data_for_revisions', 'bzrlib.smart.repository',
 
310
    'SmartServerRepositoryStreamKnitDataForRevisions')
 
311
request_handlers.register_lazy(
 
312
    'Repository.get_revision_graph', 'bzrlib.smart.repository', 'SmartServerRepositoryGetRevisionGraph')
 
313
request_handlers.register_lazy(
 
314
    'Repository.has_revision', 'bzrlib.smart.repository', 'SmartServerRequestHasRevision')
 
315
request_handlers.register_lazy(
 
316
    'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
 
317
request_handlers.register_lazy(
 
318
    'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')
 
319
request_handlers.register_lazy(
 
320
    'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
 
321
request_handlers.register_lazy(
 
322
    'Repository.tarball', 'bzrlib.smart.repository',
 
323
    'SmartServerRepositoryTarball')
 
324
request_handlers.register_lazy(
 
325
    'rmdir', 'bzrlib.smart.vfs', 'RmdirRequest')
 
326
request_handlers.register_lazy(
 
327
    'stat', 'bzrlib.smart.vfs', 'StatRequest')
 
328
request_handlers.register_lazy(
 
329
    'Transport.is_readonly', 'bzrlib.smart.request', 'SmartServerIsReadonly')
 
330
request_handlers.register_lazy(
 
331
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')