161
164
return self._combine_paths(self._path, relpath)
163
166
def _call(self, method, *args):
165
resp = self._call2(method, *args)
166
except errors.ErrorFromSmartServer, err:
167
self._translate_error(err.error_tuple)
168
self._translate_error(resp)
167
resp = self._call2(method, *args)
168
self._ensure_ok(resp)
170
170
def _call2(self, method, *args):
171
171
"""Call a method on the remote server."""
173
173
return self._client.call(method, *args)
174
174
except errors.ErrorFromSmartServer, err:
175
self._translate_error(err.error_tuple)
175
# The first argument, if present, is always a path.
177
context = {'relpath': args[0]}
180
self._translate_error(err, **context)
177
182
def _call_with_body_bytes(self, method, args, body):
178
183
"""Call a method on the remote server with body bytes."""
180
185
return self._client.call_with_body_bytes(method, args, body)
181
186
except errors.ErrorFromSmartServer, err:
182
self._translate_error(err.error_tuple)
187
# The first argument, if present, is always a path.
189
context = {'relpath': args[0]}
192
self._translate_error(err, **context)
184
194
def has(self, relpath):
185
195
"""Indicate whether a remote file of the given name exists or not.
312
321
offsets = list(offsets)
314
323
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()
318
324
coalesced = list(self._coalesce_offsets(sorted_offsets,
319
325
limit=self._max_readv_combine,
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)
326
fudge_factor=self._bytes_to_read_before_seek,
327
max_size=self._max_readv_bytes))
329
# now that we've coallesced things, avoid making enormous requests
334
if c.length + cur_len > self._max_readv_bytes:
335
requests.append(cur_request)
339
cur_request.append(c)
342
requests.append(cur_request)
343
if 'hpss' in debug.debug_flags:
344
trace.mutter('%s.readv %s offsets => %s coalesced'
345
' => %s requests (%s)',
346
self.__class__.__name__, len(offsets), len(coalesced),
347
len(requests), sum(map(len, requests)))
348
# Cache the results, but only until they have been fulfilled
350
# turn the list of offsets into a single stack to iterate
351
offset_stack = iter(offsets)
352
# using a list so it can be modified when passing down and coming back
353
next_offset = [offset_stack.next()]
354
for cur_request in requests:
356
result = self._client.call_with_body_readv_array(
357
('readv', self._remote_path(relpath),),
358
[(c.start, c.length) for c in cur_request])
359
resp, response_handler = result
360
except errors.ErrorFromSmartServer, err:
361
self._translate_error(err, relpath)
363
if resp[0] != 'readv':
364
# This should raise an exception
365
response_handler.cancel_read_body()
366
raise errors.UnexpectedSmartServerResponse(resp)
368
for res in self._handle_response(offset_stack, cur_request,
374
def _handle_response(self, offset_stack, coalesced, response_handler,
375
data_map, next_offset):
376
cur_offset_and_size = next_offset[0]
335
377
# FIXME: this should know how many bytes are needed, for clarity.
336
378
data = response_handler.read_body_bytes()
337
# Cache the results, but only until they have been fulfilled
339
380
for c_offset in coalesced:
340
381
if len(data) < c_offset.length:
341
382
raise errors.ShortReadvError(relpath, c_offset.start,
342
383
c_offset.length, actual=len(data))
343
384
for suboffset, subsize in c_offset.ranges:
344
385
key = (c_offset.start+suboffset, subsize)
345
data_map[key] = data[suboffset:suboffset+subsize]
346
data = data[c_offset.length:]
386
this_data = data[data_offset+suboffset:
387
data_offset+suboffset+subsize]
388
# Special case when the data is in-order, rather than packing
389
# into a map and then back out again. Benchmarking shows that
390
# this has 100% hit rate, but leave in the data_map work just
392
# TODO: Could we get away with using buffer() to avoid the
393
# memory copy? Callers would need to realize they may
394
# not have a real string.
395
if key == cur_offset_and_size:
396
yield cur_offset_and_size[0], this_data
397
cur_offset_and_size = next_offset[0] = offset_stack.next()
399
data_map[key] = this_data
400
data_offset += c_offset.length
348
402
# Now that we've read some data, see if we can yield anything back
349
403
while cur_offset_and_size in data_map:
350
404
this_data = data_map.pop(cur_offset_and_size)
351
405
yield cur_offset_and_size[0], this_data
352
cur_offset_and_size = offset_stack.next()
406
cur_offset_and_size = next_offset[0] = offset_stack.next()
354
408
def rename(self, rel_from, rel_to):
355
409
self._call('rename',
364
418
def rmdir(self, relpath):
365
419
resp = self._call('rmdir', self._remote_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,))
421
def _ensure_ok(self, resp):
423
raise errors.UnexpectedSmartServerResponse(resp)
425
def _translate_error(self, err, relpath=None):
426
remote._translate_error(err, path=relpath)
421
428
def disconnect(self):
422
429
self.get_smart_medium().disconnect()
424
def delete_tree(self, relpath):
425
raise errors.TransportNotPossible('readonly transport')
427
431
def stat(self, relpath):
428
432
resp = self._call2('stat', self._remote_path(relpath))
429
433
if resp[0] == 'stat':
430
434
return _SmartStat(int(resp[1]), int(resp[2], 8))
432
self._translate_error(resp)
435
raise errors.UnexpectedSmartServerResponse(resp)
434
437
## def lock_read(self, relpath):
435
438
## """Lock the given file for shared (read) access.
500
501
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
504
502
location_config = config.LocationConfig(self.base)
505
503
bzr_remote_path = location_config.get_bzr_remote_path()
506
auth = config.AuthenticationConfig()
507
user = auth.get_user('ssh', self._host, self._port)
506
508
client_medium = medium.SmartSSHClientMedium(self._host, self._port,
507
self._user, self._password, self.base,
509
user, self._password, self.base,
508
510
bzr_remote_path=bzr_remote_path)
509
return client_medium, None
511
return client_medium, (user, self._password)
512
514
class RemoteHTTPTransport(RemoteTransport):