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 breezy.bzr.smart.
20
imported from bzrlib.smart.
23
from __future__ import absolute_import
25
23
__all__ = ['RemoteTransport', 'RemoteTCPTransport', 'RemoteSSHTransport']
27
from io import BytesIO
25
from cStringIO import StringIO
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
44
42
class _SmartStat(object):
224
219
:see: Transport.get_bytes()/get_file()
226
return BytesIO(self.get_bytes(relpath))
221
return StringIO(self.get_bytes(relpath))
228
223
def get_bytes(self, relpath):
229
224
remote = self._remote_path(relpath)
231
resp, response_handler = self._client.call_expecting_body(
233
except errors.ErrorFromSmartServer as err:
226
resp, response_handler = self._client.call_expecting_body('get', remote)
227
except errors.ErrorFromSmartServer, err:
234
228
self._translate_error(err, relpath)
235
if resp != (b'ok', ):
236
230
response_handler.cancel_read_body()
237
231
raise errors.UnexpectedSmartServerResponse(resp)
238
232
return response_handler.read_body_bytes()
240
234
def _serialise_optional_mode(self, mode):
244
return ('%d' % mode).encode('ascii')
246
240
def mkdir(self, relpath, mode=None):
247
resp = self._call2(b'mkdir', self._remote_path(relpath),
248
self._serialise_optional_mode(mode))
241
resp = self._call2('mkdir', self._remote_path(relpath),
242
self._serialise_optional_mode(mode))
250
244
def open_write_stream(self, relpath, mode=None):
251
245
"""See Transport.open_write_stream."""
252
self.put_bytes(relpath, b"", mode)
246
self.put_bytes(relpath, "", mode)
253
247
result = transport.AppendBasedFileStream(self, relpath)
254
248
transport._file_streams[self.abspath(relpath)] = result
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(
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',
263
262
(self._remote_path(relpath), self._serialise_optional_mode(mode)),
265
264
self._ensure_ok(resp)
266
return len(raw_bytes)
265
return len(upload_contents)
268
def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
267
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
269
268
create_parent_dir=False,
271
270
"""See Transport.put_bytes_non_atomic."""
272
271
# FIXME: no encoding in the transport!
273
create_parent_str = b'F'
272
create_parent_str = 'F'
274
273
if create_parent_dir:
275
create_parent_str = b'T'
274
create_parent_str = 'T'
277
276
resp = self._call_with_body_bytes(
279
278
(self._remote_path(relpath), self._serialise_optional_mode(mode),
280
279
create_parent_str, self._serialise_optional_mode(dir_mode)),
282
281
self._ensure_ok(resp)
284
283
def put_file(self, relpath, upload_file, mode=None):
305
304
def append_bytes(self, relpath, bytes, mode=None):
306
305
resp = self._call_with_body_bytes(
308
307
(self._remote_path(relpath), self._serialise_optional_mode(mode)),
310
if resp[0] == b'appended':
309
if resp[0] == 'appended':
311
310
return int(resp[1])
312
311
raise errors.UnexpectedSmartServerResponse(resp)
314
313
def delete(self, relpath):
315
resp = self._call2(b'delete', self._remote_path(relpath))
314
resp = self._call2('delete', self._remote_path(relpath))
316
315
self._ensure_ok(resp)
318
317
def external_url(self):
319
"""See breezy.transport.Transport.external_url."""
318
"""See bzrlib.transport.Transport.external_url."""
320
319
# the external path for RemoteTransports is the base
333
332
sorted_offsets = sorted(offsets)
334
333
coalesced = list(self._coalesce_offsets(sorted_offsets,
335
limit=self._max_readv_combine,
336
fudge_factor=self._bytes_to_read_before_seek,
337
max_size=self._max_readv_bytes))
334
limit=self._max_readv_combine,
335
fudge_factor=self._bytes_to_read_before_seek,
336
max_size=self._max_readv_bytes))
339
338
# now that we've coallesced things, avoid making enormous requests
360
359
# turn the list of offsets into a single stack to iterate
361
360
offset_stack = iter(offsets)
362
361
# using a list so it can be modified when passing down and coming back
363
next_offset = [next(offset_stack)]
362
next_offset = [offset_stack.next()]
364
363
for cur_request in requests:
366
365
result = self._client.call_with_body_readv_array(
367
(b'readv', self._remote_path(relpath),),
366
('readv', self._remote_path(relpath),),
368
367
[(c.start, c.length) for c in cur_request])
369
368
resp, response_handler = result
370
except errors.ErrorFromSmartServer as err:
369
except errors.ErrorFromSmartServer, err:
371
370
self._translate_error(err, relpath)
373
if resp[0] != b'readv':
372
if resp[0] != 'readv':
374
373
# This should raise an exception
375
374
response_handler.cancel_read_body()
376
375
raise errors.UnexpectedSmartServerResponse(resp)
390
389
for c_offset in coalesced:
391
390
if len(data) < c_offset.length:
392
391
raise errors.ShortReadvError(relpath, c_offset.start,
393
c_offset.length, actual=len(data))
392
c_offset.length, actual=len(data))
394
393
for suboffset, subsize in c_offset.ranges:
395
key = (c_offset.start + suboffset, subsize)
396
this_data = data[data_offset + suboffset:
397
data_offset + suboffset + subsize]
394
key = (c_offset.start+suboffset, subsize)
395
this_data = data[data_offset+suboffset:
396
data_offset+suboffset+subsize]
398
397
# Special case when the data is in-order, rather than packing
399
398
# into a map and then back out again. Benchmarking shows that
400
399
# this has 100% hit rate, but leave in the data_map work just
417
412
while cur_offset_and_size in data_map:
418
413
this_data = data_map.pop(cur_offset_and_size)
419
414
yield cur_offset_and_size[0], this_data
421
cur_offset_and_size = next_offset[0] = next(offset_stack)
422
except StopIteration:
415
cur_offset_and_size = next_offset[0] = offset_stack.next()
425
417
def rename(self, rel_from, rel_to):
426
self._call(b'rename',
427
419
self._remote_path(rel_from),
428
420
self._remote_path(rel_to))
430
422
def move(self, rel_from, rel_to):
432
424
self._remote_path(rel_from),
433
425
self._remote_path(rel_to))
435
427
def rmdir(self, relpath):
436
resp = self._call(b'rmdir', self._remote_path(relpath))
428
resp = self._call('rmdir', self._remote_path(relpath))
438
430
def _ensure_ok(self, resp):
440
432
raise errors.UnexpectedSmartServerResponse(resp)
442
434
def _translate_error(self, err, relpath=None):
443
435
remote._translate_error(err, path=relpath)
445
437
def disconnect(self):
446
m = self.get_smart_medium()
438
self.get_smart_medium().disconnect()
450
440
def stat(self, relpath):
451
resp = self._call2(b'stat', self._remote_path(relpath))
452
if resp[0] == b'stat':
441
resp = self._call2('stat', self._remote_path(relpath))
442
if resp[0] == 'stat':
453
443
return _SmartStat(int(resp[1]), int(resp[2], 8))
454
444
raise errors.UnexpectedSmartServerResponse(resp)
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):
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):
464
454
## self.path = path
467
# return BogusLock(relpath)
457
## return BogusLock(relpath)
469
459
def listable(self):
472
462
def list_dir(self, relpath):
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:]]
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:]]
476
466
raise errors.UnexpectedSmartServerResponse(resp)
478
468
def iter_files_recursive(self):
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:]]
469
resp = self._call2('iter_files_recursive', self._remote_path(''))
470
if resp[0] == 'names':
482
472
raise errors.UnexpectedSmartServerResponse(resp)
520
510
def _build_medium(self):
521
511
location_config = config.LocationConfig(self.base)
522
512
bzr_remote_path = location_config.get_bzr_remote_path()
523
user = self._parsed_url.user
525
515
auth = config.AuthenticationConfig()
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,
516
user = auth.get_user('ssh', self._host, self._port)
517
ssh_params = medium.SSHParams(self._host, self._port, user,
518
self._password, bzr_remote_path)
531
519
client_medium = medium.SmartSSHClientMedium(self.base, ssh_params)
532
return client_medium, (user, self._parsed_url.password)
520
return client_medium, (user, self._password)
535
523
class RemoteHTTPTransport(RemoteTransport):
604
592
class HintingSSHTransport(transport.Transport):
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.
593
"""Simple transport that handles ssh:// and points out bzr+ssh://."""
610
595
def __init__(self, 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))
596
raise errors.UnsupportedProtocol(url,
597
'bzr supports bzr+ssh to operate over ssh, use "bzr+%s".' % url)
616
600
def get_test_permutations():
617
601
"""Return (transport, server) permutations for testing."""
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
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
621
605
return [(RemoteTCPTransport, test_server.SmartTCPServer_for_testing)]