summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/browsewin.py42
-rw-r--r--fs/commands/fscp.py2
-rw-r--r--fs/commands/fsls.py41
-rw-r--r--fs/commands/fsmkdir.py2
-rw-r--r--fs/commands/fsmount.py2
-rw-r--r--fs/commands/fsserve.py2
-rw-r--r--fs/commands/fstree.py25
-rw-r--r--fs/commands/runner.py75
-rw-r--r--fs/ftpfs.py12
-rw-r--r--fs/opener.py53
-rw-r--r--fs/sftpfs.py69
-rw-r--r--fs/wrapfs/subfs.py8
12 files changed, 232 insertions, 101 deletions
diff --git a/fs/browsewin.py b/fs/browsewin.py
index 1e1754f..60760c2 100644
--- a/fs/browsewin.py
+++ b/fs/browsewin.py
@@ -14,12 +14,13 @@ Requires wxPython.
import wx
import wx.gizmos
-import base as fs
+from fs.path import isdotfile, pathsplit
+from fs.errors import FSError
class InfoFrame(wx.Frame):
- def __init__(self, path, desc, info):
- wx.Frame.__init__(self, None, -1, style=wx.DEFAULT_FRAME_STYLE, size=(500, 500))
+ def __init__(self, parent, path, desc, info):
+ wx.Frame.__init__(self, parent, -1, style=wx.DEFAULT_FRAME_STYLE, size=(500, 500))
self.SetTitle("FS Object info - %s (%s)" % (path, desc))
@@ -34,19 +35,22 @@ class InfoFrame(wx.Frame):
self.list_ctrl.SetColumnWidth(0, 190)
self.list_ctrl.SetColumnWidth(1, 300)
- for key in keys:
- self.list_ctrl.Append((key, str(info.get(key))))
+ for key in sorted(keys, key=lambda k:k.lower()):
+ self.list_ctrl.Append((key, unicode(info.get(key))))
+
+ self.Center()
class BrowseFrame(wx.Frame):
- def __init__(self, fs):
+ def __init__(self, fs, hide_dotfiles=False):
wx.Frame.__init__(self, None, size=(1000, 600))
self.fs = fs
- self.SetTitle("FS Browser - "+str(fs))
+ self.hide_dotfiles = hide_dotfiles
+ self.SetTitle("FS Browser - " + unicode(fs))
self.tree = wx.gizmos.TreeListCtrl(self, -1, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT)
@@ -98,7 +102,19 @@ class BrowseFrame(wx.Frame):
if item_data['expanded']:
return
- paths = [(self.fs.isdir(p), p) for p in self.fs.listdir(path, absolute=True)]
+ try:
+ paths = ( [(True, p) for p in self.fs.listdir(path, absolute=True, dirs_only=True)] +
+ [(False, p) for p in self.fs.listdir(path, absolute=True, files_only=True)] )
+ except FSError, e:
+ msg = "Failed to get directory listing for %s\n\nThe following error was reported:\n\n%s" % (path, e)
+ wx.MessageDialog(self, msg, "Error listing directory", wx.OK).ShowModal()
+ paths = []
+
+
+ #paths = [(self.fs.isdir(p), p) for p in self.fs.listdir(path, absolute=True)]
+
+ if self.hide_dotfiles:
+ paths = [p for p in paths if not isdotfile(p[1])]
if not paths:
#self.tree.SetItemHasChildren(item_id, False)
@@ -109,7 +125,7 @@ class BrowseFrame(wx.Frame):
for is_dir, new_path in paths:
- name = fs.pathsplit(new_path)[-1]
+ name = pathsplit(new_path)[-1]
new_item = self.tree.AppendItem(item_id, name, data=wx.TreeItemData({'path':new_path, 'expanded':False}))
@@ -157,20 +173,22 @@ class BrowseFrame(wx.Frame):
path = item_data["path"]
info = self.fs.getinfo(path)
- info_frame = InfoFrame(path, self.fs.desc(path), info)
+ info_frame = InfoFrame(self, path, self.fs.desc(path), info)
info_frame.Show()
+ info_frame.CenterOnParent()
-def browse(fs):
+def browse(fs, hide_dotfiles=False):
"""Displays a window containing a tree control that displays an FS
object. Double-click a file/folder to display extra info.
:param fs: A filesystem object
+ :param hide_fotfiles: If True, files and folders that begin with a dot will be hidden
"""
app = wx.PySimpleApp()
- frame = BrowseFrame(fs)
+ frame = BrowseFrame(fs, hide_dotfiles=True)
frame.Show()
app.MainLoop()
diff --git a/fs/commands/fscp.py b/fs/commands/fscp.py
index abae921..48978e9 100644
--- a/fs/commands/fscp.py
+++ b/fs/commands/fscp.py
@@ -74,7 +74,7 @@ Copy SOURCE to DESTINATION"""
srcs = args[:-1]
dst = args[-1]
- dst_fs, dst_path = self.open_fs(dst, writeable=True, create=True)
+ dst_fs, dst_path = self.open_fs(dst, writeable=True, create_dir=True)
if dst_path is not None and dst_fs.isfile(dst_path):
self.error('Destination must be a directory\n')
diff --git a/fs/commands/fsls.py b/fs/commands/fsls.py
index 611d700..2cfb244 100644
--- a/fs/commands/fsls.py
+++ b/fs/commands/fsls.py
@@ -17,7 +17,9 @@ List contents of [PATH]"""
optparse.add_option('-u', '--full', dest='fullpath', action="store_true", default=False,
help="output full path", metavar="FULL")
optparse.add_option('-s', '--syspath', dest='syspath', action="store_true", default=False,
- help="output system path", metavar="SYSPATH")
+ help="output system path (if one exists)", metavar="SYSPATH")
+ optparse.add_option('-r', '--url', dest='url', action="store_true", default=False,
+ help="output URL in place of path (if one exists)", metavar="URL")
optparse.add_option('-d', '--dirsonly', dest='dirsonly', action="store_true", default=False,
help="list directories only", metavar="DIRSONLY")
optparse.add_option('-f', '--filesonly', dest='filesonly', action="store_true", default=False,
@@ -52,30 +54,36 @@ List contents of [PATH]"""
if not options.dirsonly:
file_paths.append(path)
else:
- if not options.filesonly:
+ if not options.filesonly:
dir_paths += fs.listdir(path,
wildcard=wildcard,
- full=options.fullpath,
+ full=options.fullpath or options.url,
dirs_only=True)
if not options.dirsonly:
file_paths += fs.listdir(path,
wildcard=wildcard,
- full=options.fullpath,
+ full=options.fullpath or options.url,
files_only=True)
- try:
- for fs in fs_used:
+ for fs in fs_used:
+ try:
fs.close()
- except FSError:
- pass
-
+ except FSError:
+ pass
+
if options.syspath:
- dir_paths = [fs.getsyspath(path, allow_none=True) or path for path in dir_paths]
- file_paths = [fs.getsyspath(path, allow_none=True) or path for path in file_paths]
+ # Path without a syspath, just won't be displayed
+ dir_paths = filter(None, [fs.getsyspath(path, allow_none=True) for path in dir_paths])
+ file_paths = filter(None, [fs.getsyspath(path, allow_none=True) for path in file_paths])
+
+ if options.url:
+ # Path without a syspath, just won't be displayed
+ dir_paths = filter(None, [fs.getpathurl(path, allow_none=True) for path in dir_paths])
+ file_paths = filter(None, [fs.getpathurl(path, allow_none=True) for path in file_paths])
- dirs = frozenset(dir_paths)
- paths = sorted(dir_paths + file_paths, key=lambda p:p.lower())
+ dirs = frozenset(dir_paths)
+ paths = sorted(dir_paths + file_paths, key=lambda p:p.lower())
if not options.all:
paths = [path for path in paths if not isdotfile(path)]
@@ -129,9 +137,9 @@ List contents of [PATH]"""
if options.long:
for path in paths:
if path in dirs:
- output(self.wrap_dirname(path) + '\n')
+ output((self.wrap_dirname(path), '\n'))
else:
- output(self.wrap_filename(path) + '\n')
+ output((self.wrap_filename(path), '\n'))
else:
terminal_width = self.terminal_width
@@ -158,8 +166,7 @@ List contents of [PATH]"""
num_cols -= 1
num_cols = max(1, num_cols)
columns = columnize(paths, num_cols)
- output(condense_columns(columns))
- output('\n')
+ output((condense_columns(columns), '\n'))
def run():
return FSls().run()
diff --git a/fs/commands/fsmkdir.py b/fs/commands/fsmkdir.py
index bafb42c..bb64e2c 100644
--- a/fs/commands/fsmkdir.py
+++ b/fs/commands/fsmkdir.py
@@ -14,7 +14,7 @@ Make a directory"""
def do_run(self, options, args):
for fs_url in args:
- fs, path = self.open_fs(fs_url, create=True)
+ fs, path = self.open_fs(fs_url, create_dir=True)
def run():
return FSMkdir().run()
diff --git a/fs/commands/fsmount.py b/fs/commands/fsmount.py
index b0c4f98..3305f7f 100644
--- a/fs/commands/fsmount.py
+++ b/fs/commands/fsmount.py
@@ -54,7 +54,7 @@ Mounts a file system on a system path"""
if platform.system() == 'Windows':
pass
else:
- fs, path = self.open_fs(fs_url, create=True)
+ fs, path = self.open_fs(fs_url, create_dir=True)
if path:
if not fs.isdir(path):
self.error('%s is not a directory on %s' % (fs_url. fs))
diff --git a/fs/commands/fsserve.py b/fs/commands/fsserve.py
index c1a0c2d..cfe42a9 100644
--- a/fs/commands/fsserve.py
+++ b/fs/commands/fsserve.py
@@ -8,7 +8,7 @@ from fs.utils import print_fs
class FSServe(Command):
- """fsserve [OPTION]... [PATH]
+ usage = """fsserve [OPTION]... [PATH]
Serves the contents of PATH with one of a number of methods"""
def get_optparse(self):
diff --git a/fs/commands/fstree.py b/fs/commands/fstree.py
index 166612c..7f8fe4b 100644
--- a/fs/commands/fstree.py
+++ b/fs/commands/fstree.py
@@ -15,6 +15,8 @@ Recursively display the contents of PATH in an ascii tree"""
optparse = super(FSTree, self).get_optparse()
optparse.add_option('-l', '--level', dest='depth', type="int", default=5,
help="Descend only LEVEL directories deep", metavar="LEVEL")
+ optparse.add_option('-g', '--gui', dest='gui', action='store_true', default=False,
+ help="browse the tree with a gui")
optparse.add_option('-a', '--all', dest='all', action='store_true', default=False,
help="do not hide dot files")
optparse.add_option('-d', '--dirsfirst', dest='dirsfirst', action='store_true', default=False,
@@ -26,18 +28,23 @@ Recursively display the contents of PATH in an ascii tree"""
if not args:
args = ['.']
- for fs, path, is_dir in self.get_resources(args, single=True):
- if path is not None:
- fs.opendir(path)
+ for fs, path, is_dir in self.get_resources(args, single=True):
if not is_dir:
self.error(u"'%s' is not a dir\n" % path)
return 1
- print_fs(fs, path or '',
- file_out=self.output_file,
- max_levels=options.depth,
- terminal_colors=self.is_terminal(),
- hide_dotfiles=not options.all,
- dirs_first=options.dirsfirst)
+ fs.cache_hint(True)
+ if options.gui:
+ from fs.browsewin import browse
+ if path:
+ fs = fs.opendir(path)
+ browse(fs, hide_dotfiles=not options.all)
+ else:
+ print_fs(fs, path or '',
+ file_out=self.output_file,
+ max_levels=options.depth,
+ terminal_colors=self.is_terminal(),
+ hide_dotfiles=not options.all,
+ dirs_first=options.dirsfirst)
def run():
return FSTree().run()
diff --git a/fs/commands/runner.py b/fs/commands/runner.py
index 6647aa8..bc63ca1 100644
--- a/fs/commands/runner.py
+++ b/fs/commands/runner.py
@@ -5,7 +5,7 @@ from fs.errors import FSError
from fs.path import splitext, pathsplit, isdotfile, iswildcard
import platform
from collections import defaultdict
-
+import re
if platform.system() == 'Linux' :
def getTerminalSize():
@@ -51,8 +51,11 @@ class Command(object):
self.encoding = getattr(self.output_file, 'encoding', 'utf-8') or 'utf-8'
self.verbosity_level = 0
self.terminal_colors = not sys.platform.startswith('win') and self.is_terminal()
- w, h = getTerminalSize()
- self.terminal_width = w
+ if self.is_terminal():
+ w, h = getTerminalSize()
+ self.terminal_width = w
+ else:
+ self.terminal_width = 80
self.name = self.__class__.__name__.lower()
def is_terminal(self):
@@ -74,7 +77,9 @@ class Command(object):
def wrap_filename(self, fname):
fname = _unicode(fname)
if not self.terminal_colors:
- return fname
+ return fname
+ if '://' in fname:
+ return fname
if '.' in fname:
name, ext = splitext(fname)
fname = u'%s\x1b[36m%s\x1b[0m' % (name, ext)
@@ -88,16 +93,35 @@ class Command(object):
return text
return u'\x1b[2m%s\x1b[0m' % text
+ def wrap_link(self, text):
+ if not self.terminal_colors:
+ return text
+ return u'\x1b[1;33m%s\x1b[0m' % text
+
+ def wrap_strong(self, text):
+ if not self.terminal_colors:
+ return text
+ return u'\x1b[1m%s\x1b[0m' % text
+
def wrap_table_header(self, name):
if not self.terminal_colors:
return name
return '\x1b[1;32m%s\x1b[0m' % name
- def open_fs(self, fs_url, writeable=False, create=False):
+ def highlight_fsurls(self, text):
+ if not self.terminal_colors:
+ return text
+ re_fs = r'(\S*?://\S*)'
+ def repl(matchobj):
+ fs_url = matchobj.group(0)
+ return self.wrap_link(fs_url)
+ return re.sub(re_fs, repl, text)
+
+ def open_fs(self, fs_url, writeable=False, create_dir=False):
try:
- fs, path = opener.parse(fs_url, writeable=writeable, create_dir=create)
+ fs, path = opener.parse(fs_url, writeable=writeable, create_dir=create_dir)
except OpenerError, e:
- self.error(str(e)+'\n')
+ self.error(str(e), '\n')
sys.exit(1)
fs.cache_hint(True)
return fs, path
@@ -168,12 +192,15 @@ class Command(object):
return text
- def output(self, msg, verbose=False):
- if verbose and not self.verbose:
- return
- self.output_file.write(self.text_encode(msg))
-
+ def output(self, msgs, verbose=False):
+ if verbose and not self.verbose:
+ return
+ if isinstance(msgs, basestring):
+ msgs = (msgs,)
+ for msg in msgs:
+ self.output_file.write(self.text_encode(msg))
+
def output_table(self, table, col_process=None, verbose=False):
if verbose and not self.verbose:
@@ -199,8 +226,9 @@ class Command(object):
lines.append(self.text_encode('%s\n' % ' '.join(out_col).rstrip()))
self.output(''.join(lines))
- def error(self, msg):
- self.error_file.write('%s: %s' % (self.name, self.text_encode(msg)))
+ def error(self, *msgs):
+ for msg in msgs:
+ self.error_file.write('%s: %s' % (self.name, self.text_encode(msg)))
def get_optparse(self):
optparse = OptionParser(usage=self.usage, version=self.version)
@@ -229,23 +257,23 @@ class Command(object):
for line in lines:
words = []
line_len = 0
- for word in line.split():
+ for word in line.split():
+ if word == '*':
+ word = ' *'
if line_len + len(word) > self.terminal_width:
- self.output(' '.join(words))
- self.output('\n')
+ self.output((self.highlight_fsurls(' '.join(words)), '\n'))
del words[:]
line_len = 0
words.append(word)
line_len += len(word) + 1
if words:
- self.output(' '.join(words))
+ self.output(self.highlight_fsurls(' '.join(words)))
self.output('\n')
- for names, desc in opener_table:
- self.output('\n')
+ for names, desc in opener_table:
+ self.output(('-' * self.terminal_width, '\n'))
proto = ', '.join([n+'://' for n in names])
- self.output(self.wrap_dirname('[%s]' % proto))
- self.output('\n')
+ self.output((self.wrap_dirname('[%s]' % proto), '\n\n'))
if not desc.strip():
desc = "No information available"
wrap_line(desc)
@@ -280,8 +308,7 @@ class Command(object):
if options.debug:
raise
return 1
-
-
+
if __name__ == "__main__":
command = Command()
diff --git a/fs/ftpfs.py b/fs/ftpfs.py
index aa0eec4..43cbda6 100644
--- a/fs/ftpfs.py
+++ b/fs/ftpfs.py
@@ -1035,6 +1035,15 @@ class FTPFS(FS):
pass
self.closed = True
+ def getpathurl(self, path, allow_none=False):
+ path = normpath(path)
+ credentials = '%s:%s' % (self.user, self.passwd)
+ if credentials == ':':
+ url = 'ftp://%s%s' % (self.host.rstrip('/'), abspath(path))
+ else:
+ url = 'ftp://%s@%s%s' % (credentials, self.host.rstrip('/'), abspath(path))
+ return url
+
@ftperrors
def open(self, path, mode='r'):
mode = mode.lower()
@@ -1249,6 +1258,9 @@ class FTPFS(FS):
@ftperrors
def desc(self, path):
+ url = self.getpathurl(path, allow_none=True)
+ if url:
+ return url
dirlist, fname = self._check_path(path)
if fname not in dirlist:
raise ResourceNotFoundError(path)
diff --git a/fs/opener.py b/fs/opener.py
index 38b1689..68124ab 100644
--- a/fs/opener.py
+++ b/fs/opener.py
@@ -210,7 +210,13 @@ class Opener(object):
class OSFSOpener(Opener):
names = ['osfs', 'file']
- desc = "OS filesystem opener, works with any valid system path. This is the default opener and will be used if you don't indicate which opener to use."
+ desc = """OS filesystem opener, works with any valid system path. This is the default opener and will be used if you don't indicate which opener to use.
+
+ examples:
+ * file://relative/foo/bar/baz.txt (opens a relative file)
+ * file:///home/user (opens a directory from a absolute path)
+ * osfs://~/ (open the user's home directory)
+ * foo/bar.baz (file:// is the default opener)"""
@classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
@@ -226,7 +232,12 @@ class OSFSOpener(Opener):
class ZipOpener(Opener):
names = ['zip', 'zip64']
- desc = "Opens zip files. Use zip64 for > 2 megabyte zip files, if you have a 64 bit processor.\ne.g. zip://myzip"
+ desc = """Opens zip files. Use zip64 for > 2 megabyte zip files, if you have a 64 bit processor.
+
+ examples:
+ * zip://myzip.zip (open a local zip file)
+ * zip://myzip.zip!foo/bar/insidezip.txt (reference a file insize myzip.zip)
+ * zip:ftp://ftp.example.org/myzip.zip (open a zip file stored on a ftp server)"""
@classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
@@ -263,7 +274,11 @@ class ZipOpener(Opener):
class RPCOpener(Opener):
names = ['rpc']
- desc = "An opener for filesystems server over RPC (see the fsserve command). e.g. rpc://127.0.0.1"
+ desc = """An opener for filesystems server over RPC (see the fsserve command).
+
+examples:
+rpc://127.0.0.1:8000 (opens a RPC server running on local host, port 80)
+rpc://www.example.org (opens an RPC server on www.example.org, default port 80)"""
@classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
@@ -284,7 +299,11 @@ class RPCOpener(Opener):
class FTPOpener(Opener):
names = ['ftp']
- desc = "An opener for FTP (File Transfer Protocl) servers. e.g. ftp://ftp.mozilla.org"
+ desc = """An opener for FTP (File Transfer Protocl) server
+
+examples:
+* ftp://ftp.mozilla.org (opens the root of ftp.mozilla.org)
+* ftp://ftp.example.org/foo/bar (opens /foo/bar on ftp.mozilla.org)"""
@classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
@@ -316,7 +335,11 @@ class FTPOpener(Opener):
class SFTPOpener(Opener):
names = ['sftp']
- desc = "An opener for SFTP (Secure File Transfer Protocol) servers"
+ desc = """An opener for SFTP (Secure File Transfer Protocol) servers
+
+examples:
+* sftp://username:password@example.org (opens sftp server example.org with username and password
+* sftp://example.org (opens example.org with public key authentication)"""
@classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
@@ -367,7 +390,13 @@ class SFTPOpener(Opener):
class MemOpener(Opener):
names = ['mem', 'ram']
- desc = """Creates an in-memory filesystem (very fast but contents will disappear on exit)."""
+ desc = """Creates an in-memory filesystem (very fast but contents will disappear on exit).
+Useful for creating a fast temporary filesystem for serving or mounting with fsserve or fsmount.
+NB: If you user fscp or fsmv to copy/move files here, you are effectively deleting them!
+
+examples:
+* mem:// (opens a new memory filesystem)
+* mem://foo/bar (opens a new memory filesystem with subdirectory /foo/bar) """
@classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
@@ -380,7 +409,10 @@ class MemOpener(Opener):
class DebugOpener(Opener):
names = ['debug']
- desc = "For developer -- adds debugging information to output. To use prepend an exisiting opener with debug: e.g debug:ftp://ftp.mozilla.org"
+ desc = """For developers -- adds debugging information to output.
+
+example:
+ * debug:ftp://ftp.mozilla.org (displays details of calls made to a ftp filesystem)"""
@classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
@@ -398,7 +430,12 @@ class DebugOpener(Opener):
class TempOpener(Opener):
names = ['temp']
- desc = "Creates a temporary filesystem, that is erased on exit."
+ desc = """Creates a temporary filesystem, that is erased on exit.
+Probably only useful for mounting or serving.
+NB: If you user fscp or fsmv to copy/move files here, you are effectively deleting them!
+
+example:
+* temp://"""
@classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
diff --git a/fs/sftpfs.py b/fs/sftpfs.py
index 5f1846f..23b174c 100644
--- a/fs/sftpfs.py
+++ b/fs/sftpfs.py
@@ -93,6 +93,7 @@ class SFTPFS(FS):
credentials = dict(username=username,
password=password,
pkey=pkey)
+ self.credentials = credentials
if encoding is None:
encoding = "utf8"
@@ -103,7 +104,12 @@ class SFTPFS(FS):
self._tlocal = thread_local()
self._transport = None
self._client = None
-
+
+ self.hostname = None
+ if isinstance(connection, basestring):
+ self.hostname = connection
+ elif isinstance(connection, tuple):
+ self.hostname = '%s:%s' % connection
super(SFTPFS, self).__init__()
self.root_path = abspath(normpath(root_path))
@@ -124,19 +130,18 @@ class SFTPFS(FS):
connection.start_client()
- if (password or pkey) and not connection.is_authenticated():
-
+ if not connection.is_authenticated():
try:
if pkey:
connection.auth_publickey(username, pkey)
if not connection.is_authenticated() and password:
- connection.auth_password(username, password)
+ connection.auth_password(username, password)
- if not connection.is_authenticated():
+ if not connection.is_authenticated():
self._agent_auth(connection, username)
- if not connection.is_authenticated():
+ if not connection.is_authenticated():
connection.close()
raise RemoteConnectionError('no auth')
@@ -145,8 +150,10 @@ class SFTPFS(FS):
raise RemoteConnectionError('SSH exception (%s)' % str(e), details=e)
self._transport = connection
+
+ def __unicode__(self):
+ return '<SFTPFS: %s>' % self.desc('/')
-
@classmethod
def _agent_auth(cls, transport, username):
"""
@@ -213,6 +220,21 @@ class SFTPFS(FS):
raise PathError(path,msg="Path is outside root: %(path)s")
return npath
+ def getpathurl(self, path, allow_none=False):
+ path = self._normpath(path)
+ if self.hostname is None:
+ if allow_none:
+ return None
+ raise NoPathURLError(path=path)
+ username = self.credentials.get('username', '') or ''
+ password = self.credentials.get('password', '') or ''
+ credentials = ('%s:%s' % (username, password)).rstrip(':')
+ if credentials:
+ url = 'sftp://%s@%s%s' % (credentials, self.hostname.rstrip('/'), abspath(path))
+ else:
+ url = 'sftp://%s%s' % (self.hostname.rstrip('/'), abspath(path))
+ return url
+
@convert_os_errors
def open(self,path,mode="rb",bufsize=-1):
npath = self._normpath(path)
@@ -234,8 +256,11 @@ class SFTPFS(FS):
def desc(self, path):
npath = self._normpath(path)
- addr, port = self._transport.getpeername()
- return u'%s on sftp://%s:%i' % (self.client.normalize(npath), addr, port)
+ if self.hostname:
+ return u'sftp://%s%s' % (self.hostname, path)
+ else:
+ addr, port = self._transport.getpeername()
+ return u'sftp://%s:%i%s' % (addr, port, self.client.normalize(npath))
@convert_os_errors
def exists(self,path):
@@ -282,10 +307,9 @@ class SFTPFS(FS):
if dirs_only or files_only:
attrs = self.client.listdir_attr(npath)
attrs_map = dict((a.filename, a) for a in attrs)
- paths = attrs_map.keys()
+ paths = list(attrs_map.iterkeys())
else:
- paths = self.client.listdir(npath)
-
+ paths = self.client.listdir(npath)
except IOError, e:
if getattr(e,"errno",None) == 2:
if self.isfile(path):
@@ -294,24 +318,25 @@ class SFTPFS(FS):
elif self.isfile(path):
raise ResourceInvalidError(path,msg="Can't list directory contents of a file: %(path)s")
raise
-
+
if attrs_map:
if dirs_only:
filter_paths = []
- for path, attr in attrs_map.iteritems():
+ for apath, attr in attrs_map.iteritems():
if isdir(self, path, attr.__dict__):
- filter_paths.append(path)
+ filter_paths.append(apath)
paths = filter_paths
elif files_only:
filter_paths = []
- for path, attr in attrs_map.iteritems():
- if isfile(self, path, attr.__dict__):
- filter_paths.append(path)
- paths = filter_paths
+ for apath, attr in attrs_map.iteritems():
+ if isfile(self, apath, attr.__dict__):
+ filter_paths.append(apath)
+ paths = filter_paths
for (i,p) in enumerate(paths):
if not isinstance(p,unicode):
- paths[i] = p.decode(self.encoding)
+ paths[i] = p.decode(self.encoding)
+
return self._listdir_helper(path, paths, wildcard, full, absolute, False, False)
@@ -477,7 +502,7 @@ class SFTPFS(FS):
@classmethod
def _extract_info(cls, stats):
fromtimestamp = datetime.datetime.fromtimestamp
- info = dict((k, v) for k, v in stats.iteritems() if k in cls._info_vars)
+ info = dict((k, v) for k, v in stats.iteritems() if k in cls._info_vars and not k.startswith('_'))
info['size'] = info['st_size']
ct = info.get('st_ctime')
if ct is not None:
@@ -494,7 +519,7 @@ class SFTPFS(FS):
def getinfo(self, path):
npath = self._normpath(path)
stats = self.client.stat(npath)
- info = dict((k, getattr(stats, k)) for k in dir(stats) if not k.startswith('__') )
+ info = dict((k, getattr(stats, k)) for k in dir(stats) if not k.startswith('_'))
info['size'] = info['st_size']
ct = info.get('st_ctime', None)
if ct is not None:
diff --git a/fs/wrapfs/subfs.py b/fs/wrapfs/subfs.py
index 6271f35..1b6b24a 100644
--- a/fs/wrapfs/subfs.py
+++ b/fs/wrapfs/subfs.py
@@ -30,18 +30,16 @@ class SubFS(WrapFS):
return abspath(normpath(path))[len(self.sub_dir):]
def __str__(self):
- return "%s/%s" % (self.wrapped_fs, self.sub_dir.lstrip('/'))
+ return self.wrapped_fs.desc(self.sub_dir)
def __unicode__(self):
- return u"%s/%s" % (self.wrapped_fs, self.sub_dir.lstrip('/'))
+ return u'<SubFS: %s!%s>' % (self.wrapped_fs, self.sub_dir)
def __repr__(self):
return str(self)
def desc(self, path):
- #return self.wrapped_fs.desc(join(self.sub_dir, path))
- desc = "%s!%s" % (str(self), path)
- return desc
+ return '%s!%s' % (self.wrapped_fs.desc(self.sub_dir), path)
def setcontents(self, path, data, chunk_size=64*1024):
path = self._encode(path)