22
from bzrlib import bzrdir, errors, registry, revision
27
23
from bzrlib.bundle.serializer import write_bundle
26
class SmartServerRequest(object):
27
"""Base class for request handlers.
30
def __init__(self, backing_transport):
31
self._backing_transport = backing_transport
33
def _check_enabled(self):
34
"""Raises DisabledMethod if this method is disabled."""
38
"""Mandatory extension point for SmartServerRequest subclasses.
40
Subclasses must implement this.
42
This should return a SmartServerResponse if this command expects to
45
raise NotImplementedError(self.do)
47
def execute(self, *args):
48
"""Public entry point to execute this request.
50
It will return a SmartServerResponse if the command does not expect a
53
:param *args: the arguments of the request.
58
def do_body(self, body_bytes):
59
"""Called if the client sends a body with the request.
61
Must return a SmartServerResponse.
63
# TODO: if a client erroneously sends a request that shouldn't have a
64
# body, what to do? Probably SmartServerRequestHandler should catch
65
# this NotImplementedError and translate it into a 'bad request' error
66
# to send to the client.
67
raise NotImplementedError(self.do_body)
30
70
class SmartServerResponse(object):
31
71
"""Response generated by SmartServerRequestHandler."""
56
100
# TODO: Better way of representing the body for commands that take it,
57
101
# and allow it to be streamed into the server.
59
def __init__(self, backing_transport):
103
def __init__(self, backing_transport, commands):
106
:param backing_transport: a Transport to handle requests for.
107
:param commands: a registry mapping command names to SmartServerRequest
108
subclasses. e.g. bzrlib.transport.smart.vfs.vfs_commands.
60
110
self._backing_transport = backing_transport
61
self._converted_command = False
62
self.finished_reading = False
111
self._commands = commands
63
112
self._body_bytes = ''
64
113
self.response = None
114
self.finished_reading = False
66
117
def accept_body(self, bytes):
69
This should be overriden for each command that desired body data to
70
handle the right format of that data. I.e. plain bytes, a bundle etc.
72
The deserialisation into that format should be done in the Protocol
73
object. Set self.desired_body_format to the format your method will
118
"""Accept body data."""
120
# TODO: This should be overriden for each command that desired body data
121
# to handle the right format of that data, i.e. plain bytes, a bundle,
122
# etc. The deserialisation into that format should be done in the
76
125
# default fallback is to accumulate bytes.
77
126
self._body_bytes += bytes
79
def _end_of_body_handler(self):
80
"""An unimplemented end of body handler."""
81
raise NotImplementedError(self._end_of_body_handler)
84
"""Answer a version request with my version."""
85
return SmartServerResponse(('ok', '1'))
87
def do_has(self, relpath):
88
r = self._backing_transport.has(relpath) and 'yes' or 'no'
89
return SmartServerResponse((r,))
91
def do_get(self, relpath):
92
backing_bytes = self._backing_transport.get_bytes(relpath)
93
return SmartServerResponse(('ok',), backing_bytes)
95
def _deserialise_optional_mode(self, mode):
96
# XXX: FIXME this should be on the protocol object.
102
def do_append(self, relpath, mode):
103
self._converted_command = True
104
self._relpath = relpath
105
self._mode = self._deserialise_optional_mode(mode)
106
self._end_of_body_handler = self._handle_do_append_end
108
def _handle_do_append_end(self):
109
old_length = self._backing_transport.append_bytes(
110
self._relpath, self._body_bytes, self._mode)
111
self.response = SmartServerResponse(('appended', '%d' % old_length))
113
def do_delete(self, relpath):
114
self._backing_transport.delete(relpath)
116
def do_iter_files_recursive(self, relpath):
117
transport = self._backing_transport.clone(relpath)
118
filenames = transport.iter_files_recursive()
119
return SmartServerResponse(('names',) + tuple(filenames))
121
def do_list_dir(self, relpath):
122
filenames = self._backing_transport.list_dir(relpath)
123
return SmartServerResponse(('names',) + tuple(filenames))
125
def do_mkdir(self, relpath, mode):
126
self._backing_transport.mkdir(relpath,
127
self._deserialise_optional_mode(mode))
129
def do_move(self, rel_from, rel_to):
130
self._backing_transport.move(rel_from, rel_to)
132
def do_put(self, relpath, mode):
133
self._converted_command = True
134
self._relpath = relpath
135
self._mode = self._deserialise_optional_mode(mode)
136
self._end_of_body_handler = self._handle_do_put
138
def _handle_do_put(self):
139
self._backing_transport.put_bytes(self._relpath,
140
self._body_bytes, self._mode)
141
self.response = SmartServerResponse(('ok',))
143
def _deserialise_offsets(self, text):
144
# XXX: FIXME this should be on the protocol object.
146
for line in text.split('\n'):
149
start, length = line.split(',')
150
offsets.append((int(start), int(length)))
153
def do_put_non_atomic(self, relpath, mode, create_parent, dir_mode):
154
self._converted_command = True
155
self._end_of_body_handler = self._handle_put_non_atomic
156
self._relpath = relpath
157
self._dir_mode = self._deserialise_optional_mode(dir_mode)
158
self._mode = self._deserialise_optional_mode(mode)
159
# a boolean would be nicer XXX
160
self._create_parent = (create_parent == 'T')
162
def _handle_put_non_atomic(self):
163
self._backing_transport.put_bytes_non_atomic(self._relpath,
166
create_parent_dir=self._create_parent,
167
dir_mode=self._dir_mode)
168
self.response = SmartServerResponse(('ok',))
170
def do_readv(self, relpath):
171
self._converted_command = True
172
self._end_of_body_handler = self._handle_readv_offsets
173
self._relpath = relpath
175
128
def end_of_body(self):
176
129
"""No more body data will be received."""
177
self._run_handler_code(self._end_of_body_handler, (), {})
130
self._run_handler_code(self._command.do_body, (self._body_bytes,), {})
178
131
# cannot read after this.
179
132
self.finished_reading = True
181
def _handle_readv_offsets(self):
182
"""accept offsets for a readv request."""
183
offsets = self._deserialise_offsets(self._body_bytes)
184
backing_bytes = ''.join(bytes for offset, bytes in
185
self._backing_transport.readv(self._relpath, offsets))
186
self.response = SmartServerResponse(('readv',), backing_bytes)
188
def do_rename(self, rel_from, rel_to):
189
self._backing_transport.rename(rel_from, rel_to)
191
def do_rmdir(self, relpath):
192
self._backing_transport.rmdir(relpath)
194
def do_stat(self, relpath):
195
stat = self._backing_transport.stat(relpath)
196
return SmartServerResponse(('stat', str(stat.st_size), oct(stat.st_mode)))
198
def do_get_bundle(self, path, revision_id):
199
# open transport relative to our base
200
t = self._backing_transport.clone(path)
201
control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
202
repo = control.open_repository()
203
tmpf = tempfile.TemporaryFile()
204
base_revision = revision.NULL_REVISION
205
write_bundle(repo, revision_id, base_revision, tmpf)
207
return SmartServerResponse((), tmpf.read())
209
134
def dispatch_command(self, cmd, args):
210
135
"""Deprecated compatibility method.""" # XXX XXX
211
func = getattr(self, 'do_' + cmd, None)
137
command = self._commands.get(cmd)
213
139
raise errors.SmartProtocolError("bad request %r" % (cmd,))
214
self._run_handler_code(func, args, {})
140
self._command = command(self._backing_transport)
141
self._run_handler_code(self._command.execute, args, {})
216
143
def _run_handler_code(self, callable, args, kwargs):
217
144
"""Run some handler specific code 'callable'.
193
class HelloRequest(SmartServerRequest):
194
"""Answer a version request with my version."""
197
return SmartServerResponse(('ok', '1'))
200
class GetBundleRequest(SmartServerRequest):
202
def do(self, path, revision_id):
203
# open transport relative to our base
204
t = self._backing_transport.clone(path)
205
control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
206
repo = control.open_repository()
207
tmpf = tempfile.TemporaryFile()
208
base_revision = revision.NULL_REVISION
209
write_bundle(repo, revision_id, base_revision, tmpf)
211
return SmartServerResponse((), tmpf.read())
214
# This exists solely to help RemoteObjectHacking. It should be removed
215
# eventually. It should not be considered part of the real smart server
217
class ProbeDontUseRequest(SmartServerRequest):
220
from bzrlib.bzrdir import BzrDirFormat
221
t = self._backing_transport.clone(path)
222
default_format = BzrDirFormat.get_default_format()
223
real_bzrdir = default_format.open(t, _found=True)
225
real_bzrdir._format.probe_transport(t)
226
except (errors.NotBranchError, errors.UnknownFormatError):
230
return SmartServerResponse((answer,))
233
class SmartServerIsReadonly(SmartServerRequest):
234
# XXX: this request method belongs somewhere else.
237
if self._backing_transport.is_readonly():
241
return SmartServerResponse((answer,))
244
request_handlers = registry.Registry()
245
request_handlers.register_lazy(
246
'append', 'bzrlib.smart.vfs', 'AppendRequest')
247
request_handlers.register_lazy(
248
'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
249
request_handlers.register_lazy(
250
'get', 'bzrlib.smart.vfs', 'GetRequest')
251
request_handlers.register_lazy(
252
'get_bundle', 'bzrlib.smart.request', 'GetBundleRequest')
253
request_handlers.register_lazy(
254
'has', 'bzrlib.smart.vfs', 'HasRequest')
255
request_handlers.register_lazy(
256
'hello', 'bzrlib.smart.request', 'HelloRequest')
257
request_handlers.register_lazy(
258
'iter_files_recursive', 'bzrlib.smart.vfs', 'IterFilesRecursiveRequest')
259
request_handlers.register_lazy(
260
'list_dir', 'bzrlib.smart.vfs', 'ListDirRequest')
261
request_handlers.register_lazy(
262
'mkdir', 'bzrlib.smart.vfs', 'MkdirRequest')
263
request_handlers.register_lazy(
264
'move', 'bzrlib.smart.vfs', 'MoveRequest')
265
request_handlers.register_lazy(
266
'put', 'bzrlib.smart.vfs', 'PutRequest')
267
request_handlers.register_lazy(
268
'put_non_atomic', 'bzrlib.smart.vfs', 'PutNonAtomicRequest')
269
request_handlers.register_lazy(
270
'readv', 'bzrlib.smart.vfs', 'ReadvRequest')
271
request_handlers.register_lazy(
272
'rename', 'bzrlib.smart.vfs', 'RenameRequest')
273
request_handlers.register_lazy(
274
'rmdir', 'bzrlib.smart.vfs', 'RmdirRequest')
275
request_handlers.register_lazy(
276
'stat', 'bzrlib.smart.vfs', 'StatRequest')