251
251
:return: a path prefixed with / for regular abspath-based urls, or a
252
252
path that does not begin with / for urls which begin with /~/.
254
# FIXME: share the common code across transports
254
# how does this work?
255
# it processes relpath with respect to
257
# firstly we create a path to evaluate:
258
# if relpath is an abspath or homedir path, its the entire thing
259
# otherwise we join our base with relpath
260
# then we eliminate all empty segments (double //'s) outside the first
261
# two elements of the list. This avoids problems with trailing
262
# slashes, or other abnormalities.
263
# finally we evaluate the entire path in a single pass
265
# '..' result in popping the left most already
266
# processed path (which can never be empty because of the check for
267
# abspath and homedir meaning that its not, or that we've used our
268
# path. If the pop would pop the root, we ignore it.
270
# Specific case examinations:
271
# remove the special casefor ~: if the current root is ~/ popping of it
272
# = / thus our seed for a ~ based path is ['', '~']
273
# and if we end up with [''] then we had basically ('', '..') (which is
274
# '/..' so we append '' if the length is one, and assert that the first
275
# element is still ''. Lastly, if we end with ['', '~'] as a prefix for
276
# the output, we've got a homedir path, so we strip that prefix before
277
# '/' joining the resulting list.
279
# case one: '/' -> ['', ''] cannot shrink
280
# case two: '/' + '../foo' -> ['', 'foo'] (take '', '', '..', 'foo')
281
# and pop the second '' for the '..', append 'foo'
282
# case three: '/~/' -> ['', '~', '']
283
# case four: '/~/' + '../foo' -> ['', '~', '', '..', 'foo'],
284
# and we want to get '/foo' - the empty path in the middle
285
# needs to be stripped, then normal path manipulation will
287
# case five: '/..' ['', '..'], we want ['', '']
288
# stripping '' outside the first two is ok
289
# ignore .. if its too high up
291
# lastly this code is possibly reusable by FTP, but not reusable by
292
# local paths: ~ is resolvable correctly, nor by HTTP or the smart
293
# server: ~ is resolved remotely.
295
# however, a version of this that acts on self.base is possible to be
296
# written which manipulates the URL in canonical form, and would be
297
# reusable for all transports, if a flag for allowing ~/ at all was
255
299
assert isinstance(relpath, basestring)
256
basepath = self._path.split('/')
300
relpath = urlutils.unescape(relpath)
257
303
if relpath.startswith('/'):
259
relpath = urlutils.unescape(relpath).split('/')
260
if len(basepath) > 0 and basepath[-1] == '':
261
basepath = basepath[:-1]
265
if len(basepath) == 0:
266
# In most filesystems, a request for the parent
267
# of root, just returns root.
275
path = '/'.join(basepath)
276
# mutter('relpath => remotepath %s => %s', relpath, path)
304
# abspath - normal split is fine.
305
current_path = relpath.split('/')
306
elif relpath.startswith('~/'):
307
# root is homedir based: normal split and prefix '' to remote the
309
current_path = [''].extend(relpath.split('/'))
311
# root is from the current directory:
312
if self._path.startswith('/'):
313
# abspath, take the regular split
316
# homedir based, add the '', '~' not present in self._path
317
current_path = ['', '~']
318
# add our current dir
319
current_path.extend(self._path.split('/'))
320
# add the users relpath
321
current_path.extend(relpath.split('/'))
322
# strip '' segments that are not in the first one - the leading /.
323
to_process = current_path[:1]
324
for segment in current_path[1:]:
326
to_process.append(segment)
328
# process '.' and '..' segments into output_path.
330
for segment in to_process:
332
# directory pop. Remove a directory
333
# as long as we are not at the root
334
if len(output_path) > 1:
337
# cannot pop beyond the root, so do nothing
339
continue # strip the '.' from the output.
341
# this will append '' to output_path for the root elements,
342
# which is appropriate: its why we strip '' in the first pass.
343
output_path.append(segment)
345
# check output special cases:
346
if output_path == ['']:
348
output_path = ['', '']
349
elif output_path[:2] == ['', '~']:
350
# ['', '~', ...] -> ...
351
output_path = output_path[2:]
352
path = '/'.join(output_path)
279
355
def relpath(self, abspath):
331
407
fp = self._sftp.file(path, mode='rb')
332
408
readv = getattr(fp, 'readv', None)
334
return self._sftp_readv(fp, offsets)
410
return self._sftp_readv(fp, offsets, relpath)
335
411
mutter('seek and read %s offsets', len(offsets))
336
return self._seek_and_read(fp, offsets)
412
return self._seek_and_read(fp, offsets, relpath)
337
413
except (IOError, paramiko.SSHException), e:
338
414
self._translate_io_exception(e, path, ': error retrieving')
340
def _sftp_readv(self, fp, offsets):
416
def _sftp_readv(self, fp, offsets, relpath='<unknown>'):
341
417
"""Use the readv() member of fp to do async readv.
343
419
And then read them using paramiko.readv(). paramiko.readv()