91
86
should only be used for testing purposes; normally this is
92
87
determined from the medium.
94
super(RemoteTransport, self).__init__(
95
url, _from_transport=_from_transport)
89
super(RemoteTransport, self).__init__(url,
90
_from_transport=_from_transport)
97
92
# The medium is the connection, except when we need to share it with
98
93
# other objects (RemoteBzrDir, RemoteRepository etc). In these cases
99
94
# what we want to share is really the shared connection.
101
if (_from_transport is not None
102
and isinstance(_from_transport, RemoteTransport)):
103
_client = _from_transport._client
104
elif _from_transport is None:
96
if _from_transport is None:
105
97
# If no _from_transport is specified, we need to intialize the
107
99
credentials = None
167
152
def get_smart_medium(self):
168
153
return self._get_connection()
155
@deprecated_method(one_four)
156
def get_shared_medium(self):
157
return self._get_shared_connection()
170
159
def _remote_path(self, relpath):
171
160
"""Returns the Unicode version of the absolute path for relpath."""
172
return urlutils.URL._combine_paths(self._parsed_url.path, relpath)
161
return self._combine_paths(self._path, relpath)
174
163
def _call(self, method, *args):
175
resp = self._call2(method, *args)
176
self._ensure_ok(resp)
165
resp = self._call2(method, *args)
166
except errors.ErrorFromSmartServer, err:
167
self._translate_error(err.error_tuple)
168
self._translate_error(resp)
178
170
def _call2(self, method, *args):
179
171
"""Call a method on the remote server."""
181
173
return self._client.call(method, *args)
182
174
except errors.ErrorFromSmartServer, err:
183
# The first argument, if present, is always a path.
185
context = {'relpath': args[0]}
188
self._translate_error(err, **context)
175
self._translate_error(err.error_tuple)
190
177
def _call_with_body_bytes(self, method, args, body):
191
178
"""Call a method on the remote server with body bytes."""
193
180
return self._client.call_with_body_bytes(method, args, body)
194
181
except errors.ErrorFromSmartServer, err:
195
# The first argument, if present, is always a path.
197
context = {'relpath': args[0]}
200
self._translate_error(err, **context)
182
self._translate_error(err.error_tuple)
202
184
def has(self, relpath):
203
185
"""Indicate whether a remote file of the given name exists or not.
247
230
transport._file_streams[self.abspath(relpath)] = result
250
def put_bytes(self, relpath, raw_bytes, mode=None):
251
if not isinstance(raw_bytes, str):
253
'raw_bytes must be a plain string, not %s' % type(raw_bytes))
254
resp = self._call_with_body_bytes(
233
def put_bytes(self, relpath, upload_contents, mode=None):
234
# FIXME: upload_file is probably not safe for non-ascii characters -
235
# should probably just pass all parameters as length-delimited
237
if type(upload_contents) is unicode:
238
# Although not strictly correct, we raise UnicodeEncodeError to be
239
# compatible with other transports.
240
raise UnicodeEncodeError(
241
'undefined', upload_contents, 0, 1,
242
'put_bytes must be given bytes, not unicode.')
243
resp = self._call_with_body_bytes('put',
256
244
(self._remote_path(relpath), self._serialise_optional_mode(mode)),
258
self._ensure_ok(resp)
259
return len(raw_bytes)
246
self._translate_error(resp)
247
return len(upload_contents)
261
def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
249
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
262
250
create_parent_dir=False,
264
252
"""See Transport.put_bytes_non_atomic."""
324
312
offsets = list(offsets)
326
314
sorted_offsets = sorted(offsets)
315
# turn the list of offsets into a stack
316
offset_stack = iter(offsets)
317
cur_offset_and_size = offset_stack.next()
327
318
coalesced = list(self._coalesce_offsets(sorted_offsets,
328
319
limit=self._max_readv_combine,
329
fudge_factor=self._bytes_to_read_before_seek,
330
max_size=self._max_readv_bytes))
332
# now that we've coallesced things, avoid making enormous requests
337
if c.length + cur_len > self._max_readv_bytes:
338
requests.append(cur_request)
342
cur_request.append(c)
345
requests.append(cur_request)
346
if 'hpss' in debug.debug_flags:
347
trace.mutter('%s.readv %s offsets => %s coalesced'
348
' => %s requests (%s)',
349
self.__class__.__name__, len(offsets), len(coalesced),
350
len(requests), sum(map(len, requests)))
320
fudge_factor=self._bytes_to_read_before_seek))
323
result = self._client.call_with_body_readv_array(
324
('readv', self._remote_path(relpath),),
325
[(c.start, c.length) for c in coalesced])
326
resp, response_handler = result
327
except errors.ErrorFromSmartServer, err:
328
self._translate_error(err.error_tuple)
330
if resp[0] != 'readv':
331
# This should raise an exception
332
response_handler.cancel_read_body()
333
raise errors.UnexpectedSmartServerResponse(resp)
335
# FIXME: this should know how many bytes are needed, for clarity.
336
data = response_handler.read_body_bytes()
351
337
# Cache the results, but only until they have been fulfilled
353
# turn the list of offsets into a single stack to iterate
354
offset_stack = iter(offsets)
355
# using a list so it can be modified when passing down and coming back
356
next_offset = [offset_stack.next()]
357
for cur_request in requests:
359
result = self._client.call_with_body_readv_array(
360
('readv', self._remote_path(relpath),),
361
[(c.start, c.length) for c in cur_request])
362
resp, response_handler = result
363
except errors.ErrorFromSmartServer, err:
364
self._translate_error(err, relpath)
366
if resp[0] != 'readv':
367
# This should raise an exception
368
response_handler.cancel_read_body()
369
raise errors.UnexpectedSmartServerResponse(resp)
371
for res in self._handle_response(offset_stack, cur_request,
377
def _handle_response(self, offset_stack, coalesced, response_handler,
378
data_map, next_offset):
379
cur_offset_and_size = next_offset[0]
380
# FIXME: this should know how many bytes are needed, for clarity.
381
data = response_handler.read_body_bytes()
383
339
for c_offset in coalesced:
384
340
if len(data) < c_offset.length:
385
341
raise errors.ShortReadvError(relpath, c_offset.start,
386
342
c_offset.length, actual=len(data))
387
343
for suboffset, subsize in c_offset.ranges:
388
344
key = (c_offset.start+suboffset, subsize)
389
this_data = data[data_offset+suboffset:
390
data_offset+suboffset+subsize]
391
# Special case when the data is in-order, rather than packing
392
# into a map and then back out again. Benchmarking shows that
393
# this has 100% hit rate, but leave in the data_map work just
395
# TODO: Could we get away with using buffer() to avoid the
396
# memory copy? Callers would need to realize they may
397
# not have a real string.
398
if key == cur_offset_and_size:
399
yield cur_offset_and_size[0], this_data
400
cur_offset_and_size = next_offset[0] = offset_stack.next()
402
data_map[key] = this_data
403
data_offset += c_offset.length
345
data_map[key] = data[suboffset:suboffset+subsize]
346
data = data[c_offset.length:]
405
348
# Now that we've read some data, see if we can yield anything back
406
349
while cur_offset_and_size in data_map:
407
350
this_data = data_map.pop(cur_offset_and_size)
408
351
yield cur_offset_and_size[0], this_data
409
cur_offset_and_size = next_offset[0] = offset_stack.next()
352
cur_offset_and_size = offset_stack.next()
411
354
def rename(self, rel_from, rel_to):
412
355
self._call('rename',
421
364
def rmdir(self, relpath):
422
365
resp = self._call('rmdir', self._remote_path(relpath))
424
def _ensure_ok(self, resp):
426
raise errors.UnexpectedSmartServerResponse(resp)
428
def _translate_error(self, err, relpath=None):
429
remote._translate_error(err, path=relpath)
367
def _translate_error(self, resp, orig_path=None):
368
"""Raise an exception from a response"""
375
elif what == 'NoSuchFile':
376
if orig_path is not None:
377
error_path = orig_path
380
raise errors.NoSuchFile(error_path)
381
elif what == 'error':
382
raise errors.SmartProtocolError(unicode(resp[1]))
383
elif what == 'FileExists':
384
raise errors.FileExists(resp[1])
385
elif what == 'DirectoryNotEmpty':
386
raise errors.DirectoryNotEmpty(resp[1])
387
elif what == 'ShortReadvError':
388
raise errors.ShortReadvError(resp[1], int(resp[2]),
389
int(resp[3]), int(resp[4]))
390
elif what in ('UnicodeEncodeError', 'UnicodeDecodeError'):
391
encoding = str(resp[1]) # encoding must always be a string
395
reason = str(resp[5]) # reason must always be a string
396
if val.startswith('u:'):
397
val = val[2:].decode('utf-8')
398
elif val.startswith('s:'):
399
val = val[2:].decode('base64')
400
if what == 'UnicodeDecodeError':
401
raise UnicodeDecodeError(encoding, val, start, end, reason)
402
elif what == 'UnicodeEncodeError':
403
raise UnicodeEncodeError(encoding, val, start, end, reason)
404
elif what == "ReadOnlyError":
405
raise errors.TransportNotPossible('readonly transport')
406
elif what == "ReadError":
407
if orig_path is not None:
408
error_path = orig_path
411
raise errors.ReadError(error_path)
412
elif what == "PermissionDenied":
413
if orig_path is not None:
414
error_path = orig_path
417
raise errors.PermissionDenied(error_path)
419
raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
431
421
def disconnect(self):
432
m = self.get_smart_medium()
422
self.get_smart_medium().disconnect()
424
def delete_tree(self, relpath):
425
raise errors.TransportNotPossible('readonly transport')
436
427
def stat(self, relpath):
437
428
resp = self._call2('stat', self._remote_path(relpath))
438
429
if resp[0] == 'stat':
439
430
return _SmartStat(int(resp[1]), int(resp[2], 8))
440
raise errors.UnexpectedSmartServerResponse(resp)
432
self._translate_error(resp)
442
434
## def lock_read(self, relpath):
443
435
## """Lock the given file for shared (read) access.
459
451
resp = self._call2('list_dir', self._remote_path(relpath))
460
452
if resp[0] == 'names':
461
453
return [name.encode('ascii') for name in resp[1:]]
462
raise errors.UnexpectedSmartServerResponse(resp)
455
self._translate_error(resp)
464
457
def iter_files_recursive(self):
465
458
resp = self._call2('iter_files_recursive', self._remote_path(''))
466
459
if resp[0] == 'names':
468
raise errors.UnexpectedSmartServerResponse(resp)
462
self._translate_error(resp)
471
465
class RemoteTCPTransport(RemoteTransport):
472
466
"""Connection to smart server over plain tcp.
474
468
This is essentially just a factory to get 'RemoteTransport(url,
475
469
SmartTCPClientMedium).
478
472
def _build_medium(self):
479
473
client_medium = medium.SmartTCPClientMedium(
480
self._parsed_url.host, self._parsed_url.port, self.base)
474
self._host, self._port, self.base)
481
475
return client_medium, None
506
500
def _build_medium(self):
501
# ssh will prompt the user for a password if needed and if none is
502
# provided but it will not give it back, so no credentials can be
507
504
location_config = config.LocationConfig(self.base)
508
505
bzr_remote_path = location_config.get_bzr_remote_path()
509
user = self._parsed_url.user
511
auth = config.AuthenticationConfig()
512
user = auth.get_user('ssh', self._parsed_url.host,
513
self._parsed_url.port)
514
ssh_params = medium.SSHParams(self._parsed_url.host,
515
self._parsed_url.port, user, self._parsed_url.password,
517
client_medium = medium.SmartSSHClientMedium(self.base, ssh_params)
518
return client_medium, (user, self._parsed_url.password)
506
client_medium = medium.SmartSSHClientMedium(self._host, self._port,
507
self._user, self._password, self.base,
508
bzr_remote_path=bzr_remote_path)
509
return client_medium, None
521
512
class RemoteHTTPTransport(RemoteTransport):
522
513
"""Just a way to connect between a bzr+http:// url and http://.
524
515
This connection operates slightly differently than the RemoteSSHTransport.
525
516
It uses a plain http:// transport underneath, which defines what remote
526
517
.bzr/smart URL we are connected to. From there, all paths that are sent are
575
566
_from_transport=self,
576
567
http_transport=self._http_transport)
578
def _redirected_to(self, source, target):
579
"""See transport._redirected_to"""
580
redirected = self._http_transport._redirected_to(source, target)
581
if (redirected is not None
582
and isinstance(redirected, type(self._http_transport))):
583
return RemoteHTTPTransport('bzr+' + redirected.external_url(),
584
http_transport=redirected)
586
# Either None or a transport for a different protocol
590
class HintingSSHTransport(transport.Transport):
591
"""Simple transport that handles ssh:// and points out bzr+ssh://."""
593
def __init__(self, url):
594
raise errors.UnsupportedProtocol(url,
595
'bzr supports bzr+ssh to operate over ssh, use "bzr+%s".' % url)
598
570
def get_test_permutations():
599
571
"""Return (transport, server) permutations for testing."""
600
572
### We may need a little more test framework support to construct an
601
573
### appropriate RemoteTransport in the future.
602
from bzrlib.tests import test_server
603
return [(RemoteTCPTransport, test_server.SmartTCPServer_for_testing)]
574
from bzrlib.smart import server
575
return [(RemoteTCPTransport, server.SmartTCPServer_for_testing)]