68
68
# When making a readv request, cap it at requesting 5MB of data
69
_max_readv_bytes = 5 * 1024 * 1024
69
_max_readv_bytes = 5*1024*1024
71
71
# IMPORTANT FOR IMPLEMENTORS: RemoteTransport MUST NOT be given encoding
72
72
# responsibilities: Put those on SmartClient or similar. This is vital for
102
102
# what we want to share is really the shared connection.
104
104
if (_from_transport is not None
105
and isinstance(_from_transport, RemoteTransport)):
105
and isinstance(_from_transport, RemoteTransport)):
106
106
_client = _from_transport._client
107
107
elif _from_transport is None:
108
108
# If no _from_transport is specified, we need to intialize the
151
151
def is_readonly(self):
152
152
"""Smart server transport can do read/write file operations."""
154
resp = self._call2(b'Transport.is_readonly')
154
resp = self._call2('Transport.is_readonly')
155
155
except errors.UnknownSmartMethod:
156
156
# XXX: nasty hack: servers before 0.16 don't have a
157
157
# 'Transport.is_readonly' verb, so we do what clients before 0.16
158
158
# did: assume False.
160
if resp == (b'yes', ):
160
if resp == ('yes', ):
162
elif resp == (b'no', ):
162
elif resp == ('no', ):
165
165
raise errors.UnexpectedSmartServerResponse(resp)
173
173
def _remote_path(self, relpath):
174
174
"""Returns the Unicode version of the absolute path for relpath."""
175
path = urlutils.URL._combine_paths(self._parsed_url.path, relpath)
176
if not isinstance(path, bytes):
175
return urlutils.URL._combine_paths(self._parsed_url.path, relpath)
180
177
def _call(self, method, *args):
181
178
resp = self._call2(method, *args)
188
185
except errors.ErrorFromSmartServer as err:
189
186
# The first argument, if present, is always a path.
191
context = {'relpath': args[0].decode('utf-8')}
188
context = {'relpath': args[0]}
194
191
self._translate_error(err, **context)
228
225
def get_bytes(self, relpath):
229
226
remote = self._remote_path(relpath)
231
resp, response_handler = self._client.call_expecting_body(
228
resp, response_handler = self._client.call_expecting_body('get', remote)
233
229
except errors.ErrorFromSmartServer as err:
234
230
self._translate_error(err, relpath)
235
if resp != (b'ok', ):
236
232
response_handler.cancel_read_body()
237
233
raise errors.UnexpectedSmartServerResponse(resp)
238
234
return response_handler.read_body_bytes()
240
236
def _serialise_optional_mode(self, mode):
244
return ('%d' % mode).encode('ascii')
246
242
def mkdir(self, relpath, mode=None):
247
resp = self._call2(b'mkdir', self._remote_path(relpath),
248
self._serialise_optional_mode(mode))
243
resp = self._call2('mkdir', self._remote_path(relpath),
244
self._serialise_optional_mode(mode))
250
246
def open_write_stream(self, relpath, mode=None):
251
247
"""See Transport.open_write_stream."""
252
self.put_bytes(relpath, b"", mode)
248
self.put_bytes(relpath, "", mode)
253
249
result = transport.AppendBasedFileStream(self, relpath)
254
250
transport._file_streams[self.abspath(relpath)] = result
257
253
def put_bytes(self, relpath, raw_bytes, mode=None):
258
if not isinstance(raw_bytes, bytes):
254
if not isinstance(raw_bytes, str):
260
'raw_bytes must be bytes string, not %s' % type(raw_bytes))
256
'raw_bytes must be a plain string, not %s' % type(raw_bytes))
261
257
resp = self._call_with_body_bytes(
263
259
(self._remote_path(relpath), self._serialise_optional_mode(mode)),
265
261
self._ensure_ok(resp)
271
267
"""See Transport.put_bytes_non_atomic."""
272
268
# FIXME: no encoding in the transport!
273
create_parent_str = b'F'
269
create_parent_str = 'F'
274
270
if create_parent_dir:
275
create_parent_str = b'T'
271
create_parent_str = 'T'
277
273
resp = self._call_with_body_bytes(
279
275
(self._remote_path(relpath), self._serialise_optional_mode(mode),
280
276
create_parent_str, self._serialise_optional_mode(dir_mode)),
305
301
def append_bytes(self, relpath, bytes, mode=None):
306
302
resp = self._call_with_body_bytes(
308
304
(self._remote_path(relpath), self._serialise_optional_mode(mode)),
310
if resp[0] == b'appended':
306
if resp[0] == 'appended':
311
307
return int(resp[1])
312
308
raise errors.UnexpectedSmartServerResponse(resp)
314
310
def delete(self, relpath):
315
resp = self._call2(b'delete', self._remote_path(relpath))
311
resp = self._call2('delete', self._remote_path(relpath))
316
312
self._ensure_ok(resp)
318
314
def external_url(self):
333
329
sorted_offsets = sorted(offsets)
334
330
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))
331
limit=self._max_readv_combine,
332
fudge_factor=self._bytes_to_read_before_seek,
333
max_size=self._max_readv_bytes))
339
335
# now that we've coallesced things, avoid making enormous requests
364
360
for cur_request in requests:
366
362
result = self._client.call_with_body_readv_array(
367
(b'readv', self._remote_path(relpath),),
363
('readv', self._remote_path(relpath),),
368
364
[(c.start, c.length) for c in cur_request])
369
365
resp, response_handler = result
370
366
except errors.ErrorFromSmartServer as err:
371
367
self._translate_error(err, relpath)
373
if resp[0] != b'readv':
369
if resp[0] != 'readv':
374
370
# This should raise an exception
375
371
response_handler.cancel_read_body()
376
372
raise errors.UnexpectedSmartServerResponse(resp)
390
386
for c_offset in coalesced:
391
387
if len(data) < c_offset.length:
392
388
raise errors.ShortReadvError(relpath, c_offset.start,
393
c_offset.length, actual=len(data))
389
c_offset.length, actual=len(data))
394
390
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]
391
key = (c_offset.start+suboffset, subsize)
392
this_data = data[data_offset+suboffset:
393
data_offset+suboffset+subsize]
398
394
# Special case when the data is in-order, rather than packing
399
395
# into a map and then back out again. Benchmarking shows that
400
396
# this has 100% hit rate, but leave in the data_map work just
404
400
# not have a real string.
405
401
if key == cur_offset_and_size:
406
402
yield cur_offset_and_size[0], this_data
408
cur_offset_and_size = next_offset[0] = next(
410
except StopIteration:
403
cur_offset_and_size = next_offset[0] = next(offset_stack)
413
405
data_map[key] = this_data
414
406
data_offset += c_offset.length
417
409
while cur_offset_and_size in data_map:
418
410
this_data = data_map.pop(cur_offset_and_size)
419
411
yield cur_offset_and_size[0], this_data
421
cur_offset_and_size = next_offset[0] = next(offset_stack)
422
except StopIteration:
412
cur_offset_and_size = next_offset[0] = next(offset_stack)
425
414
def rename(self, rel_from, rel_to):
426
self._call(b'rename',
427
416
self._remote_path(rel_from),
428
417
self._remote_path(rel_to))
430
419
def move(self, rel_from, rel_to):
432
421
self._remote_path(rel_from),
433
422
self._remote_path(rel_to))
435
424
def rmdir(self, relpath):
436
resp = self._call(b'rmdir', self._remote_path(relpath))
425
resp = self._call('rmdir', self._remote_path(relpath))
438
427
def _ensure_ok(self, resp):
440
429
raise errors.UnexpectedSmartServerResponse(resp)
442
431
def _translate_error(self, err, relpath=None):
450
439
def stat(self, relpath):
451
resp = self._call2(b'stat', self._remote_path(relpath))
452
if resp[0] == b'stat':
440
resp = self._call2('stat', self._remote_path(relpath))
441
if resp[0] == 'stat':
453
442
return _SmartStat(int(resp[1]), int(resp[2], 8))
454
443
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):
445
## def lock_read(self, relpath):
446
## """Lock the given file for shared (read) access.
447
## :return: A lock object, which should be passed to Transport.unlock()
449
## # The old RemoteBranch ignore lock for reading, so we will
450
## # continue that tradition and return a bogus lock object.
451
## class BogusLock(object):
452
## def __init__(self, path):
464
453
## self.path = path
467
# return BogusLock(relpath)
456
## return BogusLock(relpath)
469
458
def listable(self):
472
461
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:]]
462
resp = self._call2('list_dir', self._remote_path(relpath))
463
if resp[0] == 'names':
464
return [name.encode('ascii') for name in resp[1:]]
476
465
raise errors.UnexpectedSmartServerResponse(resp)
478
467
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:]]
468
resp = self._call2('iter_files_recursive', self._remote_path(''))
469
if resp[0] == 'names':
482
471
raise errors.UnexpectedSmartServerResponse(resp)
525
514
auth = config.AuthenticationConfig()
526
515
user = auth.get_user('ssh', self._parsed_url.host,
527
self._parsed_url.port)
516
self._parsed_url.port)
528
517
ssh_params = medium.SSHParams(self._parsed_url.host,
529
self._parsed_url.port, user, self._parsed_url.password,
518
self._parsed_url.port, user, self._parsed_url.password,
531
520
client_medium = medium.SmartSSHClientMedium(self.base, ssh_params)
532
521
return client_medium, (user, self._parsed_url.password)
593
582
"""See transport._redirected_to"""
594
583
redirected = self._http_transport._redirected_to(source, target)
595
584
if (redirected is not None
596
and isinstance(redirected, type(self._http_transport))):
585
and isinstance(redirected, type(self._http_transport))):
597
586
return RemoteHTTPTransport('bzr+' + redirected.external_url(),
598
587
http_transport=redirected)
604
593
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.
594
"""Simple transport that handles ssh:// and points out bzr+ssh://."""
610
596
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))
597
raise errors.UnsupportedProtocol(url,
598
'bzr supports bzr+ssh to operate over ssh, use "bzr+%s".' % url)
616
601
def get_test_permutations():
617
602
"""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.
603
### We may need a little more test framework support to construct an
604
### appropriate RemoteTransport in the future.
620
605
from ..tests import test_server
621
606
return [(RemoteTCPTransport, test_server.SmartTCPServer_for_testing)]