/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: Robert Collins
  • Date: 2007-04-18 08:39:02 UTC
  • mto: (2425.1.2 integration)
  • mto: This revision was merged to the branch mainline in revision 2427.
  • Revision ID: robertc@robertcollins.net-20070418083902-4o66h9fk7zeisvwa
Command objects can now declare related help topics by having _see_also
set to a list of related topic. Updated the HACKING guide entry on
documentation to be more clear about how the help for commands is
generated and to reference this new feature. (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
    """Response generated by SmartServerRequestHandler."""
 
81
 
 
82
    def __init__(self, args, body=None):
 
83
        self.args = args
 
84
        self.body = body
 
85
 
 
86
    def __eq__(self, other):
 
87
        if other is None:
 
88
            return False
 
89
        return other.args == self.args and other.body == self.body
 
90
 
 
91
    def __repr__(self):
 
92
        return "<SmartServerResponse args=%r body=%r>" % (self.args, self.body)
 
93
 
 
94
 
 
95
class SmartServerRequestHandler(object):
 
96
    """Protocol logic for smart server.
 
97
    
 
98
    This doesn't handle serialization at all, it just processes requests and
 
99
    creates responses.
 
100
    """
 
101
 
 
102
    # IMPORTANT FOR IMPLEMENTORS: It is important that SmartServerRequestHandler
 
103
    # not contain encoding or decoding logic to allow the wire protocol to vary
 
104
    # from the object protocol: we will want to tweak the wire protocol separate
 
105
    # from the object model, and ideally we will be able to do that without
 
106
    # having a SmartServerRequestHandler subclass for each wire protocol, rather
 
107
    # just a Protocol subclass.
 
108
 
 
109
    # TODO: Better way of representing the body for commands that take it,
 
110
    # and allow it to be streamed into the server.
 
111
 
 
112
    def __init__(self, backing_transport, commands):
 
113
        """Constructor.
 
114
 
 
115
        :param backing_transport: a Transport to handle requests for.
 
116
        :param commands: a registry mapping command names to SmartServerRequest
 
117
            subclasses. e.g. bzrlib.transport.smart.vfs.vfs_commands.
 
118
        """
 
119
        self._backing_transport = backing_transport
 
120
        self._commands = commands
 
121
        self._body_bytes = ''
 
122
        self.response = None
 
123
        self.finished_reading = False
 
124
        self._command = None
 
125
 
 
126
    def accept_body(self, bytes):
 
127
        """Accept body data."""
 
128
 
 
129
        # TODO: This should be overriden for each command that desired body data
 
130
        # to handle the right format of that data, i.e. plain bytes, a bundle,
 
131
        # etc.  The deserialisation into that format should be done in the
 
132
        # Protocol object.
 
133
 
 
134
        # default fallback is to accumulate bytes.
 
135
        self._body_bytes += bytes
 
136
        
 
137
    def end_of_body(self):
 
138
        """No more body data will be received."""
 
139
        self._run_handler_code(self._command.do_body, (self._body_bytes,), {})
 
140
        # cannot read after this.
 
141
        self.finished_reading = True
 
142
 
 
143
    def dispatch_command(self, cmd, args):
 
144
        """Deprecated compatibility method.""" # XXX XXX
 
145
        try:
 
146
            command = self._commands.get(cmd)
 
147
        except LookupError:
 
148
            raise errors.SmartProtocolError("bad request %r" % (cmd,))
 
149
        self._command = command(self._backing_transport)
 
150
        self._run_handler_code(self._command.execute, args, {})
 
151
 
 
152
    def _run_handler_code(self, callable, args, kwargs):
 
153
        """Run some handler specific code 'callable'.
 
154
 
 
155
        If a result is returned, it is considered to be the commands response,
 
156
        and finished_reading is set true, and its assigned to self.response.
 
157
 
 
158
        Any exceptions caught are translated and a response object created
 
159
        from them.
 
160
        """
 
161
        result = self._call_converting_errors(callable, args, kwargs)
 
162
 
 
163
        if result is not None:
 
164
            self.response = result
 
165
            self.finished_reading = True
 
166
 
 
167
    def _call_converting_errors(self, callable, args, kwargs):
 
168
        """Call callable converting errors to Response objects."""
 
169
        # XXX: most of this error conversion is VFS-related, and thus ought to
 
170
        # be in SmartServerVFSRequestHandler somewhere.
 
171
        try:
 
172
            return callable(*args, **kwargs)
 
173
        except errors.NoSuchFile, e:
 
174
            return SmartServerResponse(('NoSuchFile', e.path))
 
175
        except errors.FileExists, e:
 
176
            return SmartServerResponse(('FileExists', e.path))
 
177
        except errors.DirectoryNotEmpty, e:
 
178
            return SmartServerResponse(('DirectoryNotEmpty', e.path))
 
179
        except errors.ShortReadvError, e:
 
180
            return SmartServerResponse(('ShortReadvError',
 
181
                e.path, str(e.offset), str(e.length), str(e.actual)))
 
182
        except UnicodeError, e:
 
183
            # If it is a DecodeError, than most likely we are starting
 
184
            # with a plain string
 
185
            str_or_unicode = e.object
 
186
            if isinstance(str_or_unicode, unicode):
 
187
                # XXX: UTF-8 might have \x01 (our seperator byte) in it.  We
 
188
                # should escape it somehow.
 
189
                val = 'u:' + str_or_unicode.encode('utf-8')
 
190
            else:
 
191
                val = 's:' + str_or_unicode.encode('base64')
 
192
            # This handles UnicodeEncodeError or UnicodeDecodeError
 
193
            return SmartServerResponse((e.__class__.__name__,
 
194
                    e.encoding, val, str(e.start), str(e.end), e.reason))
 
195
        except errors.TransportNotPossible, e:
 
196
            if e.msg == "readonly transport":
 
197
                return SmartServerResponse(('ReadOnlyError', ))
 
198
            else:
 
199
                raise
 
200
 
 
201
 
 
202
class HelloRequest(SmartServerRequest):
 
203
    """Answer a version request with my version."""
 
204
 
 
205
    def do(self):
 
206
        return SmartServerResponse(('ok', '1'))
 
207
 
 
208
 
 
209
class GetBundleRequest(SmartServerRequest):
 
210
    """Get a bundle of from the null revision to the specified revision."""
 
211
 
 
212
    def do(self, path, revision_id):
 
213
        # open transport relative to our base
 
214
        t = self._backing_transport.clone(path)
 
215
        control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
 
216
        repo = control.open_repository()
 
217
        tmpf = tempfile.TemporaryFile()
 
218
        base_revision = revision.NULL_REVISION
 
219
        write_bundle(repo, revision_id, base_revision, tmpf)
 
220
        tmpf.seek(0)
 
221
        return SmartServerResponse((), tmpf.read())
 
222
 
 
223
 
 
224
class SmartServerIsReadonly(SmartServerRequest):
 
225
    # XXX: this request method belongs somewhere else.
 
226
 
 
227
    def do(self):
 
228
        if self._backing_transport.is_readonly():
 
229
            answer = 'yes'
 
230
        else:
 
231
            answer = 'no'
 
232
        return SmartServerResponse((answer,))
 
233
 
 
234
 
 
235
request_handlers = registry.Registry()
 
236
request_handlers.register_lazy(
 
237
    'append', 'bzrlib.smart.vfs', 'AppendRequest')
 
238
request_handlers.register_lazy(
 
239
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
 
240
request_handlers.register_lazy(
 
241
    'get', 'bzrlib.smart.vfs', 'GetRequest')
 
242
request_handlers.register_lazy(
 
243
    'get_bundle', 'bzrlib.smart.request', 'GetBundleRequest')
 
244
request_handlers.register_lazy(
 
245
    'has', 'bzrlib.smart.vfs', 'HasRequest')
 
246
request_handlers.register_lazy(
 
247
    'hello', 'bzrlib.smart.request', 'HelloRequest')
 
248
request_handlers.register_lazy(
 
249
    'iter_files_recursive', 'bzrlib.smart.vfs', 'IterFilesRecursiveRequest')
 
250
request_handlers.register_lazy(
 
251
    'list_dir', 'bzrlib.smart.vfs', 'ListDirRequest')
 
252
request_handlers.register_lazy(
 
253
    'mkdir', 'bzrlib.smart.vfs', 'MkdirRequest')
 
254
request_handlers.register_lazy(
 
255
    'move', 'bzrlib.smart.vfs', 'MoveRequest')
 
256
request_handlers.register_lazy(
 
257
    'put', 'bzrlib.smart.vfs', 'PutRequest')
 
258
request_handlers.register_lazy(
 
259
    'put_non_atomic', 'bzrlib.smart.vfs', 'PutNonAtomicRequest')
 
260
request_handlers.register_lazy(
 
261
    'readv', 'bzrlib.smart.vfs', 'ReadvRequest')
 
262
request_handlers.register_lazy(
 
263
    'rename', 'bzrlib.smart.vfs', 'RenameRequest')
 
264
request_handlers.register_lazy(
 
265
    'rmdir', 'bzrlib.smart.vfs', 'RmdirRequest')
 
266
request_handlers.register_lazy(
 
267
    'stat', 'bzrlib.smart.vfs', 'StatRequest')