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 brzlib.smart.
23
from __future__ import absolute_import
23
25
__all__ = ['RemoteTransport', 'RemoteTCPTransport', 'RemoteSSHTransport']
25
from io import BytesIO
27
from cStringIO import StringIO
38
from ..bzr.smart import client, medium
38
from brzlib.smart import client, medium
41
41
class _SmartStat(object):
99
99
# what we want to share is really the shared connection.
101
101
if (_from_transport is not None
102
and isinstance(_from_transport, RemoteTransport)):
102
and isinstance(_from_transport, RemoteTransport)):
103
103
_client = _from_transport._client
104
104
elif _from_transport is None:
105
105
# If no _from_transport is specified, we need to intialize the
148
148
def is_readonly(self):
149
149
"""Smart server transport can do read/write file operations."""
151
resp = self._call2(b'Transport.is_readonly')
151
resp = self._call2('Transport.is_readonly')
152
152
except errors.UnknownSmartMethod:
153
153
# XXX: nasty hack: servers before 0.16 don't have a
154
154
# 'Transport.is_readonly' verb, so we do what clients before 0.16
155
155
# did: assume False.
157
if resp == (b'yes', ):
157
if resp == ('yes', ):
159
elif resp == (b'no', ):
159
elif resp == ('no', ):
162
162
raise errors.UnexpectedSmartServerResponse(resp)
170
170
def _remote_path(self, relpath):
171
171
"""Returns the Unicode version of the absolute path for relpath."""
172
path = urlutils.URL._combine_paths(self._parsed_url.path, relpath)
173
if not isinstance(path, bytes):
172
return urlutils.URL._combine_paths(self._parsed_url.path, relpath)
177
174
def _call(self, method, *args):
178
175
resp = self._call2(method, *args)
182
179
"""Call a method on the remote server."""
184
181
return self._client.call(method, *args)
185
except errors.ErrorFromSmartServer as err:
182
except errors.ErrorFromSmartServer, err:
186
183
# The first argument, if present, is always a path.
188
context = {'relpath': args[0].decode('utf-8')}
185
context = {'relpath': args[0]}
191
188
self._translate_error(err, **context)
194
191
"""Call a method on the remote server with body bytes."""
196
193
return self._client.call_with_body_bytes(method, args, body)
197
except errors.ErrorFromSmartServer as err:
194
except errors.ErrorFromSmartServer, err:
198
195
# The first argument, if present, is always a path.
200
197
context = {'relpath': args[0]}
221
218
:see: Transport.get_bytes()/get_file()
223
return BytesIO(self.get_bytes(relpath))
220
return StringIO(self.get_bytes(relpath))
225
222
def get_bytes(self, relpath):
226
223
remote = self._remote_path(relpath)
228
resp, response_handler = self._client.call_expecting_body(
230
except errors.ErrorFromSmartServer as err:
225
resp, response_handler = self._client.call_expecting_body('get', remote)
226
except errors.ErrorFromSmartServer, err:
231
227
self._translate_error(err, relpath)
232
if resp != (b'ok', ):
233
229
response_handler.cancel_read_body()
234
230
raise errors.UnexpectedSmartServerResponse(resp)
235
231
return response_handler.read_body_bytes()
237
233
def _serialise_optional_mode(self, mode):
241
return ('%d' % mode).encode('ascii')
243
239
def mkdir(self, relpath, mode=None):
244
resp = self._call2(b'mkdir', self._remote_path(relpath),
245
self._serialise_optional_mode(mode))
240
resp = self._call2('mkdir', self._remote_path(relpath),
241
self._serialise_optional_mode(mode))
247
243
def open_write_stream(self, relpath, mode=None):
248
244
"""See Transport.open_write_stream."""
249
self.put_bytes(relpath, b"", mode)
245
self.put_bytes(relpath, "", mode)
250
246
result = transport.AppendBasedFileStream(self, relpath)
251
247
transport._file_streams[self.abspath(relpath)] = result
254
250
def put_bytes(self, relpath, raw_bytes, mode=None):
255
if not isinstance(raw_bytes, bytes):
251
if not isinstance(raw_bytes, str):
257
'raw_bytes must be bytes string, not %s' % type(raw_bytes))
253
'raw_bytes must be a plain string, not %s' % type(raw_bytes))
258
254
resp = self._call_with_body_bytes(
260
256
(self._remote_path(relpath), self._serialise_optional_mode(mode)),
262
258
self._ensure_ok(resp)
268
264
"""See Transport.put_bytes_non_atomic."""
269
265
# FIXME: no encoding in the transport!
270
create_parent_str = b'F'
266
create_parent_str = 'F'
271
267
if create_parent_dir:
272
create_parent_str = b'T'
268
create_parent_str = 'T'
274
270
resp = self._call_with_body_bytes(
276
272
(self._remote_path(relpath), self._serialise_optional_mode(mode),
277
273
create_parent_str, self._serialise_optional_mode(dir_mode)),
302
298
def append_bytes(self, relpath, bytes, mode=None):
303
299
resp = self._call_with_body_bytes(
305
301
(self._remote_path(relpath), self._serialise_optional_mode(mode)),
307
if resp[0] == b'appended':
303
if resp[0] == 'appended':
308
304
return int(resp[1])
309
305
raise errors.UnexpectedSmartServerResponse(resp)
311
307
def delete(self, relpath):
312
resp = self._call2(b'delete', self._remote_path(relpath))
308
resp = self._call2('delete', self._remote_path(relpath))
313
309
self._ensure_ok(resp)
315
311
def external_url(self):
316
"""See breezy.transport.Transport.external_url."""
312
"""See brzlib.transport.Transport.external_url."""
317
313
# the external path for RemoteTransports is the base
330
326
sorted_offsets = sorted(offsets)
331
327
coalesced = list(self._coalesce_offsets(sorted_offsets,
332
limit=self._max_readv_combine,
333
fudge_factor=self._bytes_to_read_before_seek,
334
max_size=self._max_readv_bytes))
328
limit=self._max_readv_combine,
329
fudge_factor=self._bytes_to_read_before_seek,
330
max_size=self._max_readv_bytes))
336
332
# now that we've coallesced things, avoid making enormous requests
357
353
# turn the list of offsets into a single stack to iterate
358
354
offset_stack = iter(offsets)
359
355
# using a list so it can be modified when passing down and coming back
360
next_offset = [next(offset_stack)]
356
next_offset = [offset_stack.next()]
361
357
for cur_request in requests:
363
359
result = self._client.call_with_body_readv_array(
364
(b'readv', self._remote_path(relpath),),
360
('readv', self._remote_path(relpath),),
365
361
[(c.start, c.length) for c in cur_request])
366
362
resp, response_handler = result
367
except errors.ErrorFromSmartServer as err:
363
except errors.ErrorFromSmartServer, err:
368
364
self._translate_error(err, relpath)
370
if resp[0] != b'readv':
366
if resp[0] != 'readv':
371
367
# This should raise an exception
372
368
response_handler.cancel_read_body()
373
369
raise errors.UnexpectedSmartServerResponse(resp)
387
383
for c_offset in coalesced:
388
384
if len(data) < c_offset.length:
389
385
raise errors.ShortReadvError(relpath, c_offset.start,
390
c_offset.length, actual=len(data))
386
c_offset.length, actual=len(data))
391
387
for suboffset, subsize in c_offset.ranges:
392
key = (c_offset.start + suboffset, subsize)
393
this_data = data[data_offset + suboffset:
394
data_offset + suboffset + subsize]
388
key = (c_offset.start+suboffset, subsize)
389
this_data = data[data_offset+suboffset:
390
data_offset+suboffset+subsize]
395
391
# Special case when the data is in-order, rather than packing
396
392
# into a map and then back out again. Benchmarking shows that
397
393
# this has 100% hit rate, but leave in the data_map work just
401
397
# not have a real string.
402
398
if key == cur_offset_and_size:
403
399
yield cur_offset_and_size[0], this_data
405
cur_offset_and_size = next_offset[0] = next(
407
except StopIteration:
400
cur_offset_and_size = next_offset[0] = offset_stack.next()
410
402
data_map[key] = this_data
411
403
data_offset += c_offset.length
414
406
while cur_offset_and_size in data_map:
415
407
this_data = data_map.pop(cur_offset_and_size)
416
408
yield cur_offset_and_size[0], this_data
418
cur_offset_and_size = next_offset[0] = next(offset_stack)
419
except StopIteration:
409
cur_offset_and_size = next_offset[0] = offset_stack.next()
422
411
def rename(self, rel_from, rel_to):
423
self._call(b'rename',
424
413
self._remote_path(rel_from),
425
414
self._remote_path(rel_to))
427
416
def move(self, rel_from, rel_to):
429
418
self._remote_path(rel_from),
430
419
self._remote_path(rel_to))
432
421
def rmdir(self, relpath):
433
resp = self._call(b'rmdir', self._remote_path(relpath))
422
resp = self._call('rmdir', self._remote_path(relpath))
435
424
def _ensure_ok(self, resp):
437
426
raise errors.UnexpectedSmartServerResponse(resp)
439
428
def _translate_error(self, err, relpath=None):
447
436
def stat(self, relpath):
448
resp = self._call2(b'stat', self._remote_path(relpath))
449
if resp[0] == b'stat':
437
resp = self._call2('stat', self._remote_path(relpath))
438
if resp[0] == 'stat':
450
439
return _SmartStat(int(resp[1]), int(resp[2], 8))
451
440
raise errors.UnexpectedSmartServerResponse(resp)
453
# def lock_read(self, relpath):
454
# """Lock the given file for shared (read) access.
455
# :return: A lock object, which should be passed to Transport.unlock()
457
# The old RemoteBranch ignore lock for reading, so we will
458
# continue that tradition and return a bogus lock object.
459
# class BogusLock(object):
460
# def __init__(self, path):
442
## def lock_read(self, relpath):
443
## """Lock the given file for shared (read) access.
444
## :return: A lock object, which should be passed to Transport.unlock()
446
## # The old RemoteBranch ignore lock for reading, so we will
447
## # continue that tradition and return a bogus lock object.
448
## class BogusLock(object):
449
## def __init__(self, path):
461
450
## self.path = path
464
# return BogusLock(relpath)
453
## return BogusLock(relpath)
466
455
def listable(self):
469
458
def list_dir(self, relpath):
470
resp = self._call2(b'list_dir', self._remote_path(relpath))
471
if resp[0] == b'names':
472
return [name.decode('utf-8') for name in resp[1:]]
459
resp = self._call2('list_dir', self._remote_path(relpath))
460
if resp[0] == 'names':
461
return [name.encode('ascii') for name in resp[1:]]
473
462
raise errors.UnexpectedSmartServerResponse(resp)
475
464
def iter_files_recursive(self):
476
resp = self._call2(b'iter_files_recursive', self._remote_path(''))
477
if resp[0] == b'names':
478
return [name.decode('utf-8') for name in resp[1:]]
465
resp = self._call2('iter_files_recursive', self._remote_path(''))
466
if resp[0] == 'names':
479
468
raise errors.UnexpectedSmartServerResponse(resp)
522
511
auth = config.AuthenticationConfig()
523
512
user = auth.get_user('ssh', self._parsed_url.host,
524
self._parsed_url.port)
513
self._parsed_url.port)
525
514
ssh_params = medium.SSHParams(self._parsed_url.host,
526
self._parsed_url.port, user, self._parsed_url.password,
515
self._parsed_url.port, user, self._parsed_url.password,
528
517
client_medium = medium.SmartSSHClientMedium(self.base, ssh_params)
529
518
return client_medium, (user, self._parsed_url.password)
590
579
"""See transport._redirected_to"""
591
580
redirected = self._http_transport._redirected_to(source, target)
592
581
if (redirected is not None
593
and isinstance(redirected, type(self._http_transport))):
582
and isinstance(redirected, type(self._http_transport))):
594
583
return RemoteHTTPTransport('bzr+' + redirected.external_url(),
595
584
http_transport=redirected)
601
590
class HintingSSHTransport(transport.Transport):
602
"""Simple transport that handles ssh:// and points out bzr+ssh:// and git+ssh://."""
604
# TODO(jelmer): Implement support for detecting whether the repository at the
605
# other end is a git or bzr repository.
591
"""Simple transport that handles ssh:// and points out bzr+ssh://."""
607
593
def __init__(self, url):
608
raise errors.UnsupportedProtocol(
609
url, 'Use bzr+ssh for Bazaar operations over SSH, e.g. "bzr+%s". '
610
'Use git+ssh for Git operations over SSH, e.g. "git+%s".' % (url, url))
594
raise errors.UnsupportedProtocol(url,
595
'bzr supports bzr+ssh to operate over ssh, use "bzr+%s".' % url)
613
598
def get_test_permutations():
614
599
"""Return (transport, server) permutations for testing."""
615
# We may need a little more test framework support to construct an
616
# appropriate RemoteTransport in the future.
617
from ..tests import test_server
600
### We may need a little more test framework support to construct an
601
### appropriate RemoteTransport in the future.
602
from brzlib.tests import test_server
618
603
return [(RemoteTCPTransport, test_server.SmartTCPServer_for_testing)]