~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/remote.py

Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
"""RemoteTransport client for the smart-server.
 
18
 
 
19
This module shouldn't be accessed directly.  The classes defined here should be
 
20
imported from bzrlib.smart.
 
21
"""
 
22
 
 
23
__all__ = ['RemoteTransport', 'RemoteTCPTransport', 'RemoteSSHTransport']
 
24
 
17
25
from cStringIO import StringIO
18
26
import urllib
19
27
import urlparse
22
30
    errors,
23
31
    transport,
24
32
    )
25
 
from bzrlib.smart.protocol import SmartClientRequestProtocolOne
26
 
from bzrlib.smart.medium import SmartTCPClientMedium, SmartSSHClientMedium
 
33
from bzrlib.smart import client, medium, protocol
27
34
 
28
35
# must do this otherwise urllib can't parse the urls properly :(
29
36
for scheme in ['ssh', 'bzr', 'bzr+loopback', 'bzr+ssh', 'bzr+http']:
35
42
BZR_DEFAULT_PORT = 4155
36
43
 
37
44
 
38
 
class SmartStat(object):
 
45
class _SmartStat(object):
39
46
 
40
47
    def __init__(self, size, mode):
41
48
        self.st_size = size
45
52
class RemoteTransport(transport.Transport):
46
53
    """Connection to a smart server.
47
54
 
48
 
    The connection holds references to pipes that can be used to send requests
49
 
    to the server.
 
55
    The connection holds references to the medium that can be used to send
 
56
    requests to the server.
50
57
 
51
58
    The connection has a notion of the current directory to which it's
52
59
    connected; this is incorporated in filenames passed to the server.
54
61
    This supports some higher-level RPC operations and can also be treated 
55
62
    like a Transport to do file-like operations.
56
63
 
57
 
    The connection can be made over a tcp socket, or (in future) an ssh pipe
58
 
    or a series of http requests.  There are concrete subclasses for each
59
 
    type: RemoteTCPTransport, etc.
 
64
    The connection can be made over a tcp socket, an ssh pipe or a series of
 
65
    http requests.  There are concrete subclasses for each type:
 
66
    RemoteTCPTransport, etc.
60
67
    """
61
68
 
62
69
    # IMPORTANT FOR IMPLEMENTORS: RemoteTransport MUST NOT be given encoding
109
116
 
110
117
    def is_readonly(self):
111
118
        """Smart server transport can do read/write file operations."""
112
 
        return False
113
 
                                                   
 
119
        resp = self._call2('Transport.is_readonly')
 
120
        if resp == ('yes', ):
 
121
            return True
 
122
        elif resp == ('no', ):
 
123
            return False
 
124
        else:
 
125
            self._translate_error(resp)
 
126
        assert False, 'weird response %r' % (resp,)
 
127
 
114
128
    def get_smart_client(self):
115
129
        return self._medium
116
130
 
144
158
 
145
159
    def _call2(self, method, *args):
146
160
        """Call a method on the remote server."""
147
 
        protocol = SmartClientRequestProtocolOne(self._medium.get_request())
148
 
        protocol.call(method, *args)
149
 
        return protocol.read_response_tuple()
 
161
        return client._SmartClient(self._medium).call(method, *args)
150
162
 
151
163
    def _call_with_body_bytes(self, method, args, body):
152
164
        """Call a method on the remote server with body bytes."""
153
 
        protocol = SmartClientRequestProtocolOne(self._medium.get_request())
154
 
        protocol.call_with_body_bytes((method, ) + args, body)
155
 
        return protocol.read_response_tuple()
 
165
        smart_client = client._SmartClient(self._medium)
 
166
        return smart_client.call_with_body_bytes(method, args, body)
156
167
 
157
168
    def has(self, relpath):
158
169
        """Indicate whether a remote file of the given name exists or not.
176
187
 
177
188
    def get_bytes(self, relpath):
178
189
        remote = self._remote_path(relpath)
179
 
        protocol = SmartClientRequestProtocolOne(self._medium.get_request())
180
 
        protocol.call('get', remote)
181
 
        resp = protocol.read_response_tuple(True)
 
190
        request = self._medium.get_request()
 
191
        smart_protocol = protocol.SmartClientRequestProtocolOne(request)
 
192
        smart_protocol.call('get', remote)
 
193
        resp = smart_protocol.read_response_tuple(True)
182
194
        if resp != ('ok', ):
183
 
            protocol.cancel_read_body()
 
195
            smart_protocol.cancel_read_body()
184
196
            self._translate_error(resp, relpath)
185
 
        return protocol.read_body_bytes()
 
197
        return smart_protocol.read_body_bytes()
186
198
 
187
199
    def _serialise_optional_mode(self, mode):
188
200
        if mode is None:
199
211
        # FIXME: upload_file is probably not safe for non-ascii characters -
200
212
        # should probably just pass all parameters as length-delimited
201
213
        # strings?
 
214
        if type(upload_contents) is unicode:
 
215
            # Although not strictly correct, we raise UnicodeEncodeError to be
 
216
            # compatible with other transports.
 
217
            raise UnicodeEncodeError(
 
218
                'undefined', upload_contents, 0, 1,
 
219
                'put_bytes must be given bytes, not unicode.')
202
220
        resp = self._call_with_body_bytes('put',
203
221
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
204
222
            upload_contents)
268
286
                               limit=self._max_readv_combine,
269
287
                               fudge_factor=self._bytes_to_read_before_seek))
270
288
 
271
 
        protocol = SmartClientRequestProtocolOne(self._medium.get_request())
272
 
        protocol.call_with_body_readv_array(
 
289
        request = self._medium.get_request()
 
290
        smart_protocol = protocol.SmartClientRequestProtocolOne(request)
 
291
        smart_protocol.call_with_body_readv_array(
273
292
            ('readv', self._remote_path(relpath)),
274
293
            [(c.start, c.length) for c in coalesced])
275
 
        resp = protocol.read_response_tuple(True)
 
294
        resp = smart_protocol.read_response_tuple(True)
276
295
 
277
296
        if resp[0] != 'readv':
278
297
            # This should raise an exception
279
 
            protocol.cancel_read_body()
 
298
            smart_protocol.cancel_read_body()
280
299
            self._translate_error(resp)
281
300
            return
282
301
 
283
302
        # FIXME: this should know how many bytes are needed, for clarity.
284
 
        data = protocol.read_body_bytes()
 
303
        data = smart_protocol.read_body_bytes()
285
304
        # Cache the results, but only until they have been fulfilled
286
305
        data_map = {}
287
306
        for c_offset in coalesced:
363
382
    def stat(self, relpath):
364
383
        resp = self._call2('stat', self._remote_path(relpath))
365
384
        if resp[0] == 'stat':
366
 
            return SmartStat(int(resp[1]), int(resp[2], 8))
 
385
            return _SmartStat(int(resp[1]), int(resp[2], 8))
367
386
        else:
368
387
            self._translate_error(resp)
369
388
 
398
417
            self._translate_error(resp)
399
418
 
400
419
 
401
 
 
402
420
class RemoteTCPTransport(RemoteTransport):
403
421
    """Connection to smart server over plain tcp.
404
422
    
417
435
            except (ValueError, TypeError), e:
418
436
                raise errors.InvalidURL(
419
437
                    path=url, extra="invalid port %s" % _port)
420
 
        medium = SmartTCPClientMedium(_host, _port)
421
 
        super(RemoteTCPTransport, self).__init__(url, medium=medium)
 
438
        client_medium = medium.SmartTCPClientMedium(_host, _port)
 
439
        super(RemoteTCPTransport, self).__init__(url, medium=client_medium)
422
440
 
423
441
 
424
442
class RemoteSSHTransport(RemoteTransport):
437
455
        except (ValueError, TypeError), e:
438
456
            raise errors.InvalidURL(path=url, extra="invalid port %s" % 
439
457
                _port)
440
 
        medium = SmartSSHClientMedium(_host, _port, _username, _password)
441
 
        super(RemoteSSHTransport, self).__init__(url, medium=medium)
 
458
        client_medium = medium.SmartSSHClientMedium(_host, _port,
 
459
                                                    _username, _password)
 
460
        super(RemoteSSHTransport, self).__init__(url, medium=client_medium)
442
461
 
443
462
 
444
463
class RemoteHTTPTransport(RemoteTransport):
497
516
 
498
517
def get_test_permutations():
499
518
    """Return (transport, server) permutations for testing."""
500
 
    from bzrlib.smart import server
501
519
    ### We may need a little more test framework support to construct an
502
520
    ### appropriate RemoteTransport in the future.
 
521
    from bzrlib.smart import server
503
522
    return [(RemoteTCPTransport, server.SmartTCPServer_for_testing)]