17
17
"""RemoteTransport client for the smart-server.
19
19
This module shouldn't be accessed directly. The classes defined here should be
20
imported from bzrlib.smart.
20
imported from breezy.bzr.smart.
23
from __future__ import absolute_import
23
25
__all__ = ['RemoteTransport', 'RemoteTCPTransport', 'RemoteSSHTransport']
25
from cStringIO import StringIO
27
from io import BytesIO
36
from bzrlib.smart import client, medium
37
from bzrlib.symbol_versioning import (
40
from ..sixish import PY3
41
from ..bzr.smart import client, medium
42
44
class _SmartStat(object):
219
224
:see: Transport.get_bytes()/get_file()
221
return StringIO(self.get_bytes(relpath))
226
return BytesIO(self.get_bytes(relpath))
223
228
def get_bytes(self, relpath):
224
229
remote = self._remote_path(relpath)
226
resp, response_handler = self._client.call_expecting_body('get', remote)
227
except errors.ErrorFromSmartServer, err:
231
resp, response_handler = self._client.call_expecting_body(
233
except errors.ErrorFromSmartServer as err:
228
234
self._translate_error(err, relpath)
235
if resp != (b'ok', ):
230
236
response_handler.cancel_read_body()
231
237
raise errors.UnexpectedSmartServerResponse(resp)
232
238
return response_handler.read_body_bytes()
234
240
def _serialise_optional_mode(self, mode):
244
return ('%d' % mode).encode('ascii')
240
246
def mkdir(self, relpath, mode=None):
241
resp = self._call2('mkdir', self._remote_path(relpath),
242
self._serialise_optional_mode(mode))
247
resp = self._call2(b'mkdir', self._remote_path(relpath),
248
self._serialise_optional_mode(mode))
244
250
def open_write_stream(self, relpath, mode=None):
245
251
"""See Transport.open_write_stream."""
246
self.put_bytes(relpath, "", mode)
252
self.put_bytes(relpath, b"", mode)
247
253
result = transport.AppendBasedFileStream(self, relpath)
248
254
transport._file_streams[self.abspath(relpath)] = result
251
def put_bytes(self, relpath, upload_contents, mode=None):
252
# FIXME: upload_file is probably not safe for non-ascii characters -
253
# should probably just pass all parameters as length-delimited
255
if type(upload_contents) is unicode:
256
# Although not strictly correct, we raise UnicodeEncodeError to be
257
# compatible with other transports.
258
raise UnicodeEncodeError(
259
'undefined', upload_contents, 0, 1,
260
'put_bytes must be given bytes, not unicode.')
261
resp = self._call_with_body_bytes('put',
257
def put_bytes(self, relpath, raw_bytes, mode=None):
258
if not isinstance(raw_bytes, bytes):
260
'raw_bytes must be bytes string, not %s' % type(raw_bytes))
261
resp = self._call_with_body_bytes(
262
263
(self._remote_path(relpath), self._serialise_optional_mode(mode)),
264
265
self._ensure_ok(resp)
265
return len(upload_contents)
266
return len(raw_bytes)
267
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
268
def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
268
269
create_parent_dir=False,
270
271
"""See Transport.put_bytes_non_atomic."""
271
272
# FIXME: no encoding in the transport!
272
create_parent_str = 'F'
273
create_parent_str = b'F'
273
274
if create_parent_dir:
274
create_parent_str = 'T'
275
create_parent_str = b'T'
276
277
resp = self._call_with_body_bytes(
278
279
(self._remote_path(relpath), self._serialise_optional_mode(mode),
279
280
create_parent_str, self._serialise_optional_mode(dir_mode)),
281
282
self._ensure_ok(resp)
283
284
def put_file(self, relpath, upload_file, mode=None):
304
305
def append_bytes(self, relpath, bytes, mode=None):
305
306
resp = self._call_with_body_bytes(
307
308
(self._remote_path(relpath), self._serialise_optional_mode(mode)),
309
if resp[0] == 'appended':
310
if resp[0] == b'appended':
310
311
return int(resp[1])
311
312
raise errors.UnexpectedSmartServerResponse(resp)
313
314
def delete(self, relpath):
314
resp = self._call2('delete', self._remote_path(relpath))
315
resp = self._call2(b'delete', self._remote_path(relpath))
315
316
self._ensure_ok(resp)
317
318
def external_url(self):
318
"""See bzrlib.transport.Transport.external_url."""
319
"""See breezy.transport.Transport.external_url."""
319
320
# the external path for RemoteTransports is the base
332
333
sorted_offsets = sorted(offsets)
333
334
coalesced = list(self._coalesce_offsets(sorted_offsets,
334
limit=self._max_readv_combine,
335
fudge_factor=self._bytes_to_read_before_seek,
336
max_size=self._max_readv_bytes))
335
limit=self._max_readv_combine,
336
fudge_factor=self._bytes_to_read_before_seek,
337
max_size=self._max_readv_bytes))
338
339
# now that we've coallesced things, avoid making enormous requests
359
360
# turn the list of offsets into a single stack to iterate
360
361
offset_stack = iter(offsets)
361
362
# using a list so it can be modified when passing down and coming back
362
next_offset = [offset_stack.next()]
363
next_offset = [next(offset_stack)]
363
364
for cur_request in requests:
365
366
result = self._client.call_with_body_readv_array(
366
('readv', self._remote_path(relpath),),
367
(b'readv', self._remote_path(relpath),),
367
368
[(c.start, c.length) for c in cur_request])
368
369
resp, response_handler = result
369
except errors.ErrorFromSmartServer, err:
370
except errors.ErrorFromSmartServer as err:
370
371
self._translate_error(err, relpath)
372
if resp[0] != 'readv':
373
if resp[0] != b'readv':
373
374
# This should raise an exception
374
375
response_handler.cancel_read_body()
375
376
raise errors.UnexpectedSmartServerResponse(resp)
389
390
for c_offset in coalesced:
390
391
if len(data) < c_offset.length:
391
392
raise errors.ShortReadvError(relpath, c_offset.start,
392
c_offset.length, actual=len(data))
393
c_offset.length, actual=len(data))
393
394
for suboffset, subsize in c_offset.ranges:
394
key = (c_offset.start+suboffset, subsize)
395
this_data = data[data_offset+suboffset:
396
data_offset+suboffset+subsize]
395
key = (c_offset.start + suboffset, subsize)
396
this_data = data[data_offset + suboffset:
397
data_offset + suboffset + subsize]
397
398
# Special case when the data is in-order, rather than packing
398
399
# into a map and then back out again. Benchmarking shows that
399
400
# this has 100% hit rate, but leave in the data_map work just
412
417
while cur_offset_and_size in data_map:
413
418
this_data = data_map.pop(cur_offset_and_size)
414
419
yield cur_offset_and_size[0], this_data
415
cur_offset_and_size = next_offset[0] = offset_stack.next()
421
cur_offset_and_size = next_offset[0] = next(offset_stack)
422
except StopIteration:
417
425
def rename(self, rel_from, rel_to):
426
self._call(b'rename',
419
427
self._remote_path(rel_from),
420
428
self._remote_path(rel_to))
422
430
def move(self, rel_from, rel_to):
424
432
self._remote_path(rel_from),
425
433
self._remote_path(rel_to))
427
435
def rmdir(self, relpath):
428
resp = self._call('rmdir', self._remote_path(relpath))
436
resp = self._call(b'rmdir', self._remote_path(relpath))
430
438
def _ensure_ok(self, resp):
432
440
raise errors.UnexpectedSmartServerResponse(resp)
434
442
def _translate_error(self, err, relpath=None):
435
443
remote._translate_error(err, path=relpath)
437
445
def disconnect(self):
438
self.get_smart_medium().disconnect()
446
m = self.get_smart_medium()
440
450
def stat(self, relpath):
441
resp = self._call2('stat', self._remote_path(relpath))
442
if resp[0] == 'stat':
451
resp = self._call2(b'stat', self._remote_path(relpath))
452
if resp[0] == b'stat':
443
453
return _SmartStat(int(resp[1]), int(resp[2], 8))
444
454
raise errors.UnexpectedSmartServerResponse(resp)
446
## def lock_read(self, relpath):
447
## """Lock the given file for shared (read) access.
448
## :return: A lock object, which should be passed to Transport.unlock()
450
## # The old RemoteBranch ignore lock for reading, so we will
451
## # continue that tradition and return a bogus lock object.
452
## class BogusLock(object):
453
## def __init__(self, path):
456
# def lock_read(self, relpath):
457
# """Lock the given file for shared (read) access.
458
# :return: A lock object, which should be passed to Transport.unlock()
460
# The old RemoteBranch ignore lock for reading, so we will
461
# continue that tradition and return a bogus lock object.
462
# class BogusLock(object):
463
# def __init__(self, path):
454
464
## self.path = path
457
## return BogusLock(relpath)
467
# return BogusLock(relpath)
459
469
def listable(self):
462
472
def list_dir(self, relpath):
463
resp = self._call2('list_dir', self._remote_path(relpath))
464
if resp[0] == 'names':
465
return [name.encode('ascii') for name in resp[1:]]
473
resp = self._call2(b'list_dir', self._remote_path(relpath))
474
if resp[0] == b'names':
475
return [name.decode('utf-8') if PY3 else name for name in resp[1:]]
466
476
raise errors.UnexpectedSmartServerResponse(resp)
468
478
def iter_files_recursive(self):
469
resp = self._call2('iter_files_recursive', self._remote_path(''))
470
if resp[0] == 'names':
479
resp = self._call2(b'iter_files_recursive', self._remote_path(''))
480
if resp[0] == b'names':
481
return [name.decode('utf-8') if PY3 else name for name in resp[1:]]
472
482
raise errors.UnexpectedSmartServerResponse(resp)
510
520
def _build_medium(self):
511
521
location_config = config.LocationConfig(self.base)
512
522
bzr_remote_path = location_config.get_bzr_remote_path()
523
user = self._parsed_url.user
515
525
auth = config.AuthenticationConfig()
516
user = auth.get_user('ssh', self._host, self._port)
517
client_medium = medium.SmartSSHClientMedium(self._host, self._port,
518
user, self._password, self.base,
519
bzr_remote_path=bzr_remote_path)
520
return client_medium, (user, self._password)
526
user = auth.get_user('ssh', self._parsed_url.host,
527
self._parsed_url.port)
528
ssh_params = medium.SSHParams(self._parsed_url.host,
529
self._parsed_url.port, user, self._parsed_url.password,
531
client_medium = medium.SmartSSHClientMedium(self.base, ssh_params)
532
return client_medium, (user, self._parsed_url.password)
523
535
class RemoteHTTPTransport(RemoteTransport):
592
604
class HintingSSHTransport(transport.Transport):
593
"""Simple transport that handles ssh:// and points out bzr+ssh://."""
605
"""Simple transport that handles ssh:// and points out bzr+ssh:// and git+ssh://."""
607
# TODO(jelmer): Implement support for detecting whether the repository at the
608
# other end is a git or bzr repository.
595
610
def __init__(self, url):
596
raise errors.UnsupportedProtocol(url,
597
'bzr supports bzr+ssh to operate over ssh, use "bzr+%s".' % url)
611
raise errors.UnsupportedProtocol(
612
url, 'Use bzr+ssh for Bazaar operations over SSH, e.g. "bzr+%s". '
613
'Use git+ssh for Git operations over SSH, e.g. "git+%s".' % (url, url))
600
616
def get_test_permutations():
601
617
"""Return (transport, server) permutations for testing."""
602
### We may need a little more test framework support to construct an
603
### appropriate RemoteTransport in the future.
604
from bzrlib.tests import test_server
618
# We may need a little more test framework support to construct an
619
# appropriate RemoteTransport in the future.
620
from ..tests import test_server
605
621
return [(RemoteTCPTransport, test_server.SmartTCPServer_for_testing)]