~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/remote.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-04-17 00:59:30 UTC
  • mfrom: (1551.15.4 Aaron's mergeable stuff)
  • Revision ID: pqm@pqm.ubuntu.com-20070417005930-rofskshyjsfzrahh
Fix ftp transport with servers that don't support atomic rename

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
 
 
25
17
from cStringIO import StringIO
26
18
import urllib
27
19
import urlparse
29
21
from bzrlib import (
30
22
    errors,
31
23
    transport,
32
 
    urlutils,
33
24
    )
34
 
from bzrlib.smart import client, medium, protocol
 
25
from bzrlib.smart.protocol import SmartClientRequestProtocolOne
 
26
from bzrlib.smart.medium import SmartTCPClientMedium, SmartSSHClientMedium
35
27
 
36
28
# must do this otherwise urllib can't parse the urls properly :(
37
29
for scheme in ['ssh', 'bzr', 'bzr+loopback', 'bzr+ssh', 'bzr+http']:
40
32
 
41
33
 
42
34
# Port 4155 is the default port for bzr://, registered with IANA.
43
 
BZR_DEFAULT_INTERFACE = '0.0.0.0'
44
35
BZR_DEFAULT_PORT = 4155
45
36
 
46
37
 
47
 
class _SmartStat(object):
 
38
class SmartStat(object):
48
39
 
49
40
    def __init__(self, size, mode):
50
41
        self.st_size = size
54
45
class RemoteTransport(transport.Transport):
55
46
    """Connection to a smart server.
56
47
 
57
 
    The connection holds references to the medium that can be used to send
58
 
    requests to the server.
 
48
    The connection holds references to pipes that can be used to send requests
 
49
    to the server.
59
50
 
60
51
    The connection has a notion of the current directory to which it's
61
52
    connected; this is incorporated in filenames passed to the server.
63
54
    This supports some higher-level RPC operations and can also be treated 
64
55
    like a Transport to do file-like operations.
65
56
 
66
 
    The connection can be made over a tcp socket, an ssh pipe or a series of
67
 
    http requests.  There are concrete subclasses for each type:
68
 
    RemoteTCPTransport, etc.
 
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.
69
60
    """
70
61
 
71
62
    # IMPORTANT FOR IMPLEMENTORS: RemoteTransport MUST NOT be given encoding
74
65
    # RemoteTransport is an adapter from the Transport object model to the 
75
66
    # SmartClient model, not an encoder.
76
67
 
77
 
    def __init__(self, url, clone_from=None, medium=None, _client=None):
 
68
    def __init__(self, url, clone_from=None, medium=None):
78
69
        """Constructor.
79
70
 
80
 
        :param clone_from: Another RemoteTransport instance that this one is
81
 
            being cloned from.  Attributes such as credentials and the medium
82
 
            will be reused.
83
71
        :param medium: The medium to use for this RemoteTransport. This must be
84
72
            supplied if clone_from is None.
85
 
        :param _client: Override the _SmartClient used by this transport.  This
86
 
            should only be used for testing purposes; normally this is
87
 
            determined from the medium.
88
73
        """
89
74
        ### Technically super() here is faulty because Transport's __init__
90
75
        ### fails to take 2 parameters, and if super were to choose a silly
103
88
            # reuse same connection
104
89
            self._medium = clone_from._medium
105
90
        assert self._medium is not None
106
 
        if _client is None:
107
 
            self._client = client._SmartClient(self._medium)
108
 
        else:
109
 
            self._client = _client
110
91
 
111
92
    def abspath(self, relpath):
112
93
        """Return the full url to the given relative path.
128
109
 
129
110
    def is_readonly(self):
130
111
        """Smart server transport can do read/write file operations."""
131
 
        resp = self._call2('Transport.is_readonly')
132
 
        if resp == ('yes', ):
133
 
            return True
134
 
        elif resp == ('no', ):
135
 
            return False
136
 
        elif (resp == ('error', "Generic bzr smart protocol error: "
137
 
                                "bad request 'Transport.is_readonly'") or
138
 
              resp == ('error', "Generic bzr smart protocol error: "
139
 
                                "bad request u'Transport.is_readonly'")):
140
 
            # XXX: nasty hack: servers before 0.16 don't have a
141
 
            # 'Transport.is_readonly' verb, so we do what clients before 0.16
142
 
            # did: assume False.
143
 
            return False
144
 
        else:
145
 
            self._translate_error(resp)
146
 
        raise errors.UnexpectedSmartServerResponse(resp)
147
 
 
 
112
        return False
 
113
                                                   
148
114
    def get_smart_client(self):
149
115
        return self._medium
150
116
 
178
144
 
179
145
    def _call2(self, method, *args):
180
146
        """Call a method on the remote server."""
181
 
        return self._client.call(method, *args)
 
147
        protocol = SmartClientRequestProtocolOne(self._medium.get_request())
 
148
        protocol.call(method, *args)
 
149
        return protocol.read_response_tuple()
182
150
 
183
151
    def _call_with_body_bytes(self, method, args, body):
184
152
        """Call a method on the remote server with body bytes."""
185
 
        return self._client.call_with_body_bytes(method, args, body)
 
153
        protocol = SmartClientRequestProtocolOne(self._medium.get_request())
 
154
        protocol.call_with_body_bytes((method, ) + args, body)
 
155
        return protocol.read_response_tuple()
186
156
 
187
157
    def has(self, relpath):
188
158
        """Indicate whether a remote file of the given name exists or not.
206
176
 
207
177
    def get_bytes(self, relpath):
208
178
        remote = self._remote_path(relpath)
209
 
        request = self._medium.get_request()
210
 
        smart_protocol = protocol.SmartClientRequestProtocolOne(request)
211
 
        smart_protocol.call('get', remote)
212
 
        resp = smart_protocol.read_response_tuple(True)
 
179
        protocol = SmartClientRequestProtocolOne(self._medium.get_request())
 
180
        protocol.call('get', remote)
 
181
        resp = protocol.read_response_tuple(True)
213
182
        if resp != ('ok', ):
214
 
            smart_protocol.cancel_read_body()
 
183
            protocol.cancel_read_body()
215
184
            self._translate_error(resp, relpath)
216
 
        return smart_protocol.read_body_bytes()
 
185
        return protocol.read_body_bytes()
217
186
 
218
187
    def _serialise_optional_mode(self, mode):
219
188
        if mode is None:
230
199
        # FIXME: upload_file is probably not safe for non-ascii characters -
231
200
        # should probably just pass all parameters as length-delimited
232
201
        # strings?
233
 
        if type(upload_contents) is unicode:
234
 
            # Although not strictly correct, we raise UnicodeEncodeError to be
235
 
            # compatible with other transports.
236
 
            raise UnicodeEncodeError(
237
 
                'undefined', upload_contents, 0, 1,
238
 
                'put_bytes must be given bytes, not unicode.')
239
202
        resp = self._call_with_body_bytes('put',
240
203
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
241
204
            upload_contents)
305
268
                               limit=self._max_readv_combine,
306
269
                               fudge_factor=self._bytes_to_read_before_seek))
307
270
 
308
 
        request = self._medium.get_request()
309
 
        smart_protocol = protocol.SmartClientRequestProtocolOne(request)
310
 
        smart_protocol.call_with_body_readv_array(
 
271
        protocol = SmartClientRequestProtocolOne(self._medium.get_request())
 
272
        protocol.call_with_body_readv_array(
311
273
            ('readv', self._remote_path(relpath)),
312
274
            [(c.start, c.length) for c in coalesced])
313
 
        resp = smart_protocol.read_response_tuple(True)
 
275
        resp = protocol.read_response_tuple(True)
314
276
 
315
277
        if resp[0] != 'readv':
316
278
            # This should raise an exception
317
 
            smart_protocol.cancel_read_body()
 
279
            protocol.cancel_read_body()
318
280
            self._translate_error(resp)
319
281
            return
320
282
 
321
283
        # FIXME: this should know how many bytes are needed, for clarity.
322
 
        data = smart_protocol.read_body_bytes()
 
284
        data = protocol.read_body_bytes()
323
285
        # Cache the results, but only until they have been fulfilled
324
286
        data_map = {}
325
287
        for c_offset in coalesced:
389
351
                raise UnicodeEncodeError(encoding, val, start, end, reason)
390
352
        elif what == "ReadOnlyError":
391
353
            raise errors.TransportNotPossible('readonly transport')
392
 
        elif what == "ReadError":
393
 
            if orig_path is not None:
394
 
                error_path = orig_path
395
 
            else:
396
 
                error_path = resp[1]
397
 
            raise errors.ReadError(error_path)
398
354
        else:
399
355
            raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
400
356
 
407
363
    def stat(self, relpath):
408
364
        resp = self._call2('stat', self._remote_path(relpath))
409
365
        if resp[0] == 'stat':
410
 
            return _SmartStat(int(resp[1]), int(resp[2], 8))
 
366
            return SmartStat(int(resp[1]), int(resp[2], 8))
411
367
        else:
412
368
            self._translate_error(resp)
413
369
 
442
398
            self._translate_error(resp)
443
399
 
444
400
 
 
401
 
445
402
class RemoteTCPTransport(RemoteTransport):
446
403
    """Connection to smart server over plain tcp.
447
404
    
460
417
            except (ValueError, TypeError), e:
461
418
                raise errors.InvalidURL(
462
419
                    path=url, extra="invalid port %s" % _port)
463
 
        client_medium = medium.SmartTCPClientMedium(_host, _port)
464
 
        super(RemoteTCPTransport, self).__init__(url, medium=client_medium)
 
420
        medium = SmartTCPClientMedium(_host, _port)
 
421
        super(RemoteTCPTransport, self).__init__(url, medium=medium)
465
422
 
466
423
 
467
424
class RemoteSSHTransport(RemoteTransport):
480
437
        except (ValueError, TypeError), e:
481
438
            raise errors.InvalidURL(path=url, extra="invalid port %s" % 
482
439
                _port)
483
 
        client_medium = medium.SmartSSHClientMedium(_host, _port,
484
 
                                                    _username, _password)
485
 
        super(RemoteSSHTransport, self).__init__(url, medium=client_medium)
 
440
        medium = SmartSSHClientMedium(_host, _port, _username, _password)
 
441
        super(RemoteSSHTransport, self).__init__(url, medium=medium)
486
442
 
487
443
 
488
444
class RemoteHTTPTransport(RemoteTransport):
509
465
 
510
466
    def _remote_path(self, relpath):
511
467
        """After connecting HTTP Transport only deals in relative URLs."""
512
 
        # Adjust the relpath based on which URL this smart transport is
513
 
        # connected to.
514
 
        base = urlutils.normalize_url(self._http_transport.base)
515
 
        url = urlutils.join(self.base[len('bzr+'):], relpath)
516
 
        url = urlutils.normalize_url(url)
517
 
        return urlutils.relative_url(base, url)
 
468
        if relpath == '.':
 
469
            return ''
 
470
        else:
 
471
            return relpath
518
472
 
519
473
    def abspath(self, relpath):
520
474
        """Return the full url to the given relative path.
530
484
        This is re-implemented rather than using the default
531
485
        RemoteTransport.clone() because we must be careful about the underlying
532
486
        http transport.
533
 
 
534
 
        Also, the cloned smart transport will POST to the same .bzr/smart
535
 
        location as this transport (although obviously the relative paths in the
536
 
        smart requests may be different).  This is so that the server doesn't
537
 
        have to handle .bzr/smart requests at arbitrary places inside .bzr
538
 
        directories, just at the initial URL the user uses.
539
 
 
540
 
        The exception is parent paths (i.e. relative_url of "..").
541
487
        """
542
488
        if relative_url:
543
489
            abs_url = self.abspath(relative_url)
544
490
        else:
545
491
            abs_url = self.base
546
 
        # We either use the exact same http_transport (for child locations), or
547
 
        # a clone of the underlying http_transport (for parent locations).  This
548
 
        # means we share the connection.
549
 
        norm_base = urlutils.normalize_url(self.base)
550
 
        norm_abs_url = urlutils.normalize_url(abs_url)
551
 
        normalized_rel_url = urlutils.relative_url(norm_base, norm_abs_url)
552
 
        if normalized_rel_url == ".." or normalized_rel_url.startswith("../"):
553
 
            http_transport = self._http_transport.clone(normalized_rel_url)
554
 
        else:
555
 
            http_transport = self._http_transport
556
 
        return RemoteHTTPTransport(abs_url, http_transport=http_transport)
 
492
        # By cloning the underlying http_transport, we are able to share the
 
493
        # connection.
 
494
        new_transport = self._http_transport.clone(relative_url)
 
495
        return RemoteHTTPTransport(abs_url, http_transport=new_transport)
557
496
 
558
497
 
559
498
def get_test_permutations():
560
499
    """Return (transport, server) permutations for testing."""
 
500
    from bzrlib.smart import server
561
501
    ### We may need a little more test framework support to construct an
562
502
    ### appropriate RemoteTransport in the future.
563
 
    from bzrlib.smart import server
564
503
    return [(RemoteTCPTransport, server.SmartTCPServer_for_testing)]