/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2400.1.3 by Andrew Bennetts
Split smart transport code into several separate modules.
1
import tempfile
2
3
from bzrlib import (
4
    bzrdir,
5
    errors,
6
    revision
7
    )
8
from bzrlib.bundle.serializer import write_bundle
9
10
11
class SmartServerResponse(object):
12
    """Response generated by SmartServerRequestHandler."""
13
14
    def __init__(self, args, body=None):
15
        self.args = args
16
        self.body = body
17
18
# XXX: TODO: Create a SmartServerRequestHandler which will take the responsibility
19
# for delivering the data for a request. This could be done with as the
20
# StreamServer, though that would create conflation between request and response
21
# which may be undesirable.
22
23
class SmartServerRequestHandler(object):
24
    """Protocol logic for smart server.
25
    
26
    This doesn't handle serialization at all, it just processes requests and
27
    creates responses.
28
    """
29
30
    # IMPORTANT FOR IMPLEMENTORS: It is important that SmartServerRequestHandler
31
    # not contain encoding or decoding logic to allow the wire protocol to vary
32
    # from the object protocol: we will want to tweak the wire protocol separate
33
    # from the object model, and ideally we will be able to do that without
34
    # having a SmartServerRequestHandler subclass for each wire protocol, rather
35
    # just a Protocol subclass.
36
37
    # TODO: Better way of representing the body for commands that take it,
38
    # and allow it to be streamed into the server.
39
    
40
    def __init__(self, backing_transport):
41
        self._backing_transport = backing_transport
42
        self._converted_command = False
43
        self.finished_reading = False
44
        self._body_bytes = ''
45
        self.response = None
46
47
    def accept_body(self, bytes):
48
        """Accept body data.
49
50
        This should be overriden for each command that desired body data to
51
        handle the right format of that data. I.e. plain bytes, a bundle etc.
52
53
        The deserialisation into that format should be done in the Protocol
54
        object. Set self.desired_body_format to the format your method will
55
        handle.
56
        """
57
        # default fallback is to accumulate bytes.
58
        self._body_bytes += bytes
59
        
60
    def _end_of_body_handler(self):
61
        """An unimplemented end of body handler."""
62
        raise NotImplementedError(self._end_of_body_handler)
63
        
64
    def do_hello(self):
65
        """Answer a version request with my version."""
66
        return SmartServerResponse(('ok', '1'))
67
68
    def do_has(self, relpath):
69
        r = self._backing_transport.has(relpath) and 'yes' or 'no'
70
        return SmartServerResponse((r,))
71
72
    def do_get(self, relpath):
73
        backing_bytes = self._backing_transport.get_bytes(relpath)
74
        return SmartServerResponse(('ok',), backing_bytes)
75
76
    def _deserialise_optional_mode(self, mode):
77
        # XXX: FIXME this should be on the protocol object.
78
        if mode == '':
79
            return None
80
        else:
81
            return int(mode)
82
83
    def do_append(self, relpath, mode):
84
        self._converted_command = True
85
        self._relpath = relpath
86
        self._mode = self._deserialise_optional_mode(mode)
87
        self._end_of_body_handler = self._handle_do_append_end
88
    
89
    def _handle_do_append_end(self):
90
        old_length = self._backing_transport.append_bytes(
91
            self._relpath, self._body_bytes, self._mode)
92
        self.response = SmartServerResponse(('appended', '%d' % old_length))
93
94
    def do_delete(self, relpath):
95
        self._backing_transport.delete(relpath)
96
97
    def do_iter_files_recursive(self, relpath):
98
        transport = self._backing_transport.clone(relpath)
99
        filenames = transport.iter_files_recursive()
100
        return SmartServerResponse(('names',) + tuple(filenames))
101
102
    def do_list_dir(self, relpath):
103
        filenames = self._backing_transport.list_dir(relpath)
104
        return SmartServerResponse(('names',) + tuple(filenames))
105
106
    def do_mkdir(self, relpath, mode):
107
        self._backing_transport.mkdir(relpath,
108
                                      self._deserialise_optional_mode(mode))
109
110
    def do_move(self, rel_from, rel_to):
111
        self._backing_transport.move(rel_from, rel_to)
112
113
    def do_put(self, relpath, mode):
114
        self._converted_command = True
115
        self._relpath = relpath
116
        self._mode = self._deserialise_optional_mode(mode)
117
        self._end_of_body_handler = self._handle_do_put
118
119
    def _handle_do_put(self):
120
        self._backing_transport.put_bytes(self._relpath,
121
                self._body_bytes, self._mode)
122
        self.response = SmartServerResponse(('ok',))
123
124
    def _deserialise_offsets(self, text):
125
        # XXX: FIXME this should be on the protocol object.
126
        offsets = []
127
        for line in text.split('\n'):
128
            if not line:
129
                continue
130
            start, length = line.split(',')
131
            offsets.append((int(start), int(length)))
132
        return offsets
133
134
    def do_put_non_atomic(self, relpath, mode, create_parent, dir_mode):
135
        self._converted_command = True
136
        self._end_of_body_handler = self._handle_put_non_atomic
137
        self._relpath = relpath
138
        self._dir_mode = self._deserialise_optional_mode(dir_mode)
139
        self._mode = self._deserialise_optional_mode(mode)
140
        # a boolean would be nicer XXX
141
        self._create_parent = (create_parent == 'T')
142
143
    def _handle_put_non_atomic(self):
144
        self._backing_transport.put_bytes_non_atomic(self._relpath,
145
                self._body_bytes,
146
                mode=self._mode,
147
                create_parent_dir=self._create_parent,
148
                dir_mode=self._dir_mode)
149
        self.response = SmartServerResponse(('ok',))
150
151
    def do_readv(self, relpath):
152
        self._converted_command = True
153
        self._end_of_body_handler = self._handle_readv_offsets
154
        self._relpath = relpath
155
156
    def end_of_body(self):
157
        """No more body data will be received."""
158
        self._run_handler_code(self._end_of_body_handler, (), {})
159
        # cannot read after this.
160
        self.finished_reading = True
161
162
    def _handle_readv_offsets(self):
163
        """accept offsets for a readv request."""
164
        offsets = self._deserialise_offsets(self._body_bytes)
165
        backing_bytes = ''.join(bytes for offset, bytes in
166
            self._backing_transport.readv(self._relpath, offsets))
167
        self.response = SmartServerResponse(('readv',), backing_bytes)
168
        
169
    def do_rename(self, rel_from, rel_to):
170
        self._backing_transport.rename(rel_from, rel_to)
171
172
    def do_rmdir(self, relpath):
173
        self._backing_transport.rmdir(relpath)
174
175
    def do_stat(self, relpath):
176
        stat = self._backing_transport.stat(relpath)
177
        return SmartServerResponse(('stat', str(stat.st_size), oct(stat.st_mode)))
178
        
179
    def do_get_bundle(self, path, revision_id):
180
        # open transport relative to our base
181
        t = self._backing_transport.clone(path)
182
        control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
183
        repo = control.open_repository()
184
        tmpf = tempfile.TemporaryFile()
185
        base_revision = revision.NULL_REVISION
186
        write_bundle(repo, revision_id, base_revision, tmpf)
187
        tmpf.seek(0)
188
        return SmartServerResponse((), tmpf.read())
189
190
    def dispatch_command(self, cmd, args):
191
        """Deprecated compatibility method.""" # XXX XXX
192
        func = getattr(self, 'do_' + cmd, None)
193
        if func is None:
194
            raise errors.SmartProtocolError("bad request %r" % (cmd,))
195
        self._run_handler_code(func, args, {})
196
197
    def _run_handler_code(self, callable, args, kwargs):
198
        """Run some handler specific code 'callable'.
199
200
        If a result is returned, it is considered to be the commands response,
201
        and finished_reading is set true, and its assigned to self.response.
202
203
        Any exceptions caught are translated and a response object created
204
        from them.
205
        """
206
        result = self._call_converting_errors(callable, args, kwargs)
207
        if result is not None:
208
            self.response = result
209
            self.finished_reading = True
210
        # handle unconverted commands
211
        if not self._converted_command:
212
            self.finished_reading = True
213
            if result is None:
214
                self.response = SmartServerResponse(('ok',))
215
216
    def _call_converting_errors(self, callable, args, kwargs):
217
        """Call callable converting errors to Response objects."""
218
        try:
219
            return callable(*args, **kwargs)
220
        except errors.NoSuchFile, e:
221
            return SmartServerResponse(('NoSuchFile', e.path))
222
        except errors.FileExists, e:
223
            return SmartServerResponse(('FileExists', e.path))
224
        except errors.DirectoryNotEmpty, e:
225
            return SmartServerResponse(('DirectoryNotEmpty', e.path))
226
        except errors.ShortReadvError, e:
227
            return SmartServerResponse(('ShortReadvError',
228
                e.path, str(e.offset), str(e.length), str(e.actual)))
229
        except UnicodeError, e:
230
            # If it is a DecodeError, than most likely we are starting
231
            # with a plain string
232
            str_or_unicode = e.object
233
            if isinstance(str_or_unicode, unicode):
234
                # XXX: UTF-8 might have \x01 (our seperator byte) in it.  We
235
                # should escape it somehow.
236
                val = 'u:' + str_or_unicode.encode('utf-8')
237
            else:
238
                val = 's:' + str_or_unicode.encode('base64')
239
            # This handles UnicodeEncodeError or UnicodeDecodeError
240
            return SmartServerResponse((e.__class__.__name__,
241
                    e.encoding, val, str(e.start), str(e.end), e.reason))
242
        except errors.TransportNotPossible, e:
243
            if e.msg == "readonly transport":
244
                return SmartServerResponse(('ReadOnlyError', ))
245
            else:
246
                raise
247
248
249