Commit e69d8929 by willmcgugan

Command improvements

parent a143bd9a
...@@ -14,12 +14,13 @@ Requires wxPython. ...@@ -14,12 +14,13 @@ Requires wxPython.
import wx import wx
import wx.gizmos import wx.gizmos
import base as fs from fs.path import isdotfile, pathsplit
from fs.errors import FSError
class InfoFrame(wx.Frame): class InfoFrame(wx.Frame):
def __init__(self, path, desc, info): def __init__(self, parent, path, desc, info):
wx.Frame.__init__(self, None, -1, style=wx.DEFAULT_FRAME_STYLE, size=(500, 500)) wx.Frame.__init__(self, parent, -1, style=wx.DEFAULT_FRAME_STYLE, size=(500, 500))
self.SetTitle("FS Object info - %s (%s)" % (path, desc)) self.SetTitle("FS Object info - %s (%s)" % (path, desc))
...@@ -34,19 +35,22 @@ class InfoFrame(wx.Frame): ...@@ -34,19 +35,22 @@ class InfoFrame(wx.Frame):
self.list_ctrl.SetColumnWidth(0, 190) self.list_ctrl.SetColumnWidth(0, 190)
self.list_ctrl.SetColumnWidth(1, 300) self.list_ctrl.SetColumnWidth(1, 300)
for key in keys: for key in sorted(keys, key=lambda k:k.lower()):
self.list_ctrl.Append((key, str(info.get(key)))) self.list_ctrl.Append((key, unicode(info.get(key))))
self.Center()
class BrowseFrame(wx.Frame): class BrowseFrame(wx.Frame):
def __init__(self, fs): def __init__(self, fs, hide_dotfiles=False):
wx.Frame.__init__(self, None, size=(1000, 600)) wx.Frame.__init__(self, None, size=(1000, 600))
self.fs = fs 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) self.tree = wx.gizmos.TreeListCtrl(self, -1, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT)
...@@ -98,7 +102,19 @@ class BrowseFrame(wx.Frame): ...@@ -98,7 +102,19 @@ class BrowseFrame(wx.Frame):
if item_data['expanded']: if item_data['expanded']:
return 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: if not paths:
#self.tree.SetItemHasChildren(item_id, False) #self.tree.SetItemHasChildren(item_id, False)
...@@ -109,7 +125,7 @@ class BrowseFrame(wx.Frame): ...@@ -109,7 +125,7 @@ class BrowseFrame(wx.Frame):
for is_dir, new_path in paths: 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})) new_item = self.tree.AppendItem(item_id, name, data=wx.TreeItemData({'path':new_path, 'expanded':False}))
...@@ -157,20 +173,22 @@ class BrowseFrame(wx.Frame): ...@@ -157,20 +173,22 @@ class BrowseFrame(wx.Frame):
path = item_data["path"] path = item_data["path"]
info = self.fs.getinfo(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.Show()
info_frame.CenterOnParent()
def browse(fs): def browse(fs, hide_dotfiles=False):
"""Displays a window containing a tree control that displays an FS """Displays a window containing a tree control that displays an FS
object. Double-click a file/folder to display extra info. object. Double-click a file/folder to display extra info.
:param fs: A filesystem object :param fs: A filesystem object
:param hide_fotfiles: If True, files and folders that begin with a dot will be hidden
""" """
app = wx.PySimpleApp() app = wx.PySimpleApp()
frame = BrowseFrame(fs) frame = BrowseFrame(fs, hide_dotfiles=True)
frame.Show() frame.Show()
app.MainLoop() app.MainLoop()
......
...@@ -74,7 +74,7 @@ Copy SOURCE to DESTINATION""" ...@@ -74,7 +74,7 @@ Copy SOURCE to DESTINATION"""
srcs = args[:-1] srcs = args[:-1]
dst = 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): if dst_path is not None and dst_fs.isfile(dst_path):
self.error('Destination must be a directory\n') self.error('Destination must be a directory\n')
......
...@@ -17,7 +17,9 @@ List contents of [PATH]""" ...@@ -17,7 +17,9 @@ List contents of [PATH]"""
optparse.add_option('-u', '--full', dest='fullpath', action="store_true", default=False, optparse.add_option('-u', '--full', dest='fullpath', action="store_true", default=False,
help="output full path", metavar="FULL") help="output full path", metavar="FULL")
optparse.add_option('-s', '--syspath', dest='syspath', action="store_true", default=False, 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, optparse.add_option('-d', '--dirsonly', dest='dirsonly', action="store_true", default=False,
help="list directories only", metavar="DIRSONLY") help="list directories only", metavar="DIRSONLY")
optparse.add_option('-f', '--filesonly', dest='filesonly', action="store_true", default=False, optparse.add_option('-f', '--filesonly', dest='filesonly', action="store_true", default=False,
...@@ -55,24 +57,30 @@ List contents of [PATH]""" ...@@ -55,24 +57,30 @@ List contents of [PATH]"""
if not options.filesonly: if not options.filesonly:
dir_paths += fs.listdir(path, dir_paths += fs.listdir(path,
wildcard=wildcard, wildcard=wildcard,
full=options.fullpath, full=options.fullpath or options.url,
dirs_only=True) dirs_only=True)
if not options.dirsonly: if not options.dirsonly:
file_paths += fs.listdir(path, file_paths += fs.listdir(path,
wildcard=wildcard, wildcard=wildcard,
full=options.fullpath, full=options.fullpath or options.url,
files_only=True) files_only=True)
try:
for fs in fs_used: for fs in fs_used:
try:
fs.close() fs.close()
except FSError: except FSError:
pass pass
if options.syspath: if options.syspath:
dir_paths = [fs.getsyspath(path, allow_none=True) or path for path in dir_paths] # Path without a syspath, just won't be displayed
file_paths = [fs.getsyspath(path, allow_none=True) or path for path in file_paths] 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) dirs = frozenset(dir_paths)
paths = sorted(dir_paths + file_paths, key=lambda p:p.lower()) paths = sorted(dir_paths + file_paths, key=lambda p:p.lower())
...@@ -129,9 +137,9 @@ List contents of [PATH]""" ...@@ -129,9 +137,9 @@ List contents of [PATH]"""
if options.long: if options.long:
for path in paths: for path in paths:
if path in dirs: if path in dirs:
output(self.wrap_dirname(path) + '\n') output((self.wrap_dirname(path), '\n'))
else: else:
output(self.wrap_filename(path) + '\n') output((self.wrap_filename(path), '\n'))
else: else:
terminal_width = self.terminal_width terminal_width = self.terminal_width
...@@ -158,8 +166,7 @@ List contents of [PATH]""" ...@@ -158,8 +166,7 @@ List contents of [PATH]"""
num_cols -= 1 num_cols -= 1
num_cols = max(1, num_cols) num_cols = max(1, num_cols)
columns = columnize(paths, num_cols) columns = columnize(paths, num_cols)
output(condense_columns(columns)) output((condense_columns(columns), '\n'))
output('\n')
def run(): def run():
return FSls().run() return FSls().run()
......
...@@ -14,7 +14,7 @@ Make a directory""" ...@@ -14,7 +14,7 @@ Make a directory"""
def do_run(self, options, args): def do_run(self, options, args):
for fs_url in 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(): def run():
return FSMkdir().run() return FSMkdir().run()
......
...@@ -54,7 +54,7 @@ Mounts a file system on a system path""" ...@@ -54,7 +54,7 @@ Mounts a file system on a system path"""
if platform.system() == 'Windows': if platform.system() == 'Windows':
pass pass
else: else:
fs, path = self.open_fs(fs_url, create=True) fs, path = self.open_fs(fs_url, create_dir=True)
if path: if path:
if not fs.isdir(path): if not fs.isdir(path):
self.error('%s is not a directory on %s' % (fs_url. fs)) self.error('%s is not a directory on %s' % (fs_url. fs))
......
...@@ -8,7 +8,7 @@ from fs.utils import print_fs ...@@ -8,7 +8,7 @@ from fs.utils import print_fs
class FSServe(Command): class FSServe(Command):
"""fsserve [OPTION]... [PATH] usage = """fsserve [OPTION]... [PATH]
Serves the contents of PATH with one of a number of methods""" Serves the contents of PATH with one of a number of methods"""
def get_optparse(self): def get_optparse(self):
......
...@@ -15,6 +15,8 @@ Recursively display the contents of PATH in an ascii tree""" ...@@ -15,6 +15,8 @@ Recursively display the contents of PATH in an ascii tree"""
optparse = super(FSTree, self).get_optparse() optparse = super(FSTree, self).get_optparse()
optparse.add_option('-l', '--level', dest='depth', type="int", default=5, optparse.add_option('-l', '--level', dest='depth', type="int", default=5,
help="Descend only LEVEL directories deep", metavar="LEVEL") 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, optparse.add_option('-a', '--all', dest='all', action='store_true', default=False,
help="do not hide dot files") help="do not hide dot files")
optparse.add_option('-d', '--dirsfirst', dest='dirsfirst', action='store_true', default=False, optparse.add_option('-d', '--dirsfirst', dest='dirsfirst', action='store_true', default=False,
...@@ -27,11 +29,16 @@ Recursively display the contents of PATH in an ascii tree""" ...@@ -27,11 +29,16 @@ Recursively display the contents of PATH in an ascii tree"""
args = ['.'] args = ['.']
for fs, path, is_dir in self.get_resources(args, single=True): for fs, path, is_dir in self.get_resources(args, single=True):
if path is not None:
fs.opendir(path)
if not is_dir: if not is_dir:
self.error(u"'%s' is not a dir\n" % path) self.error(u"'%s' is not a dir\n" % path)
return 1 return 1
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 '', print_fs(fs, path or '',
file_out=self.output_file, file_out=self.output_file,
max_levels=options.depth, max_levels=options.depth,
......
...@@ -5,7 +5,7 @@ from fs.errors import FSError ...@@ -5,7 +5,7 @@ from fs.errors import FSError
from fs.path import splitext, pathsplit, isdotfile, iswildcard from fs.path import splitext, pathsplit, isdotfile, iswildcard
import platform import platform
from collections import defaultdict from collections import defaultdict
import re
if platform.system() == 'Linux' : if platform.system() == 'Linux' :
def getTerminalSize(): def getTerminalSize():
...@@ -51,8 +51,11 @@ class Command(object): ...@@ -51,8 +51,11 @@ class Command(object):
self.encoding = getattr(self.output_file, 'encoding', 'utf-8') or 'utf-8' self.encoding = getattr(self.output_file, 'encoding', 'utf-8') or 'utf-8'
self.verbosity_level = 0 self.verbosity_level = 0
self.terminal_colors = not sys.platform.startswith('win') and self.is_terminal() self.terminal_colors = not sys.platform.startswith('win') and self.is_terminal()
if self.is_terminal():
w, h = getTerminalSize() w, h = getTerminalSize()
self.terminal_width = w self.terminal_width = w
else:
self.terminal_width = 80
self.name = self.__class__.__name__.lower() self.name = self.__class__.__name__.lower()
def is_terminal(self): def is_terminal(self):
...@@ -75,6 +78,8 @@ class Command(object): ...@@ -75,6 +78,8 @@ class Command(object):
fname = _unicode(fname) fname = _unicode(fname)
if not self.terminal_colors: if not self.terminal_colors:
return fname return fname
if '://' in fname:
return fname
if '.' in fname: if '.' in fname:
name, ext = splitext(fname) name, ext = splitext(fname)
fname = u'%s\x1b[36m%s\x1b[0m' % (name, ext) fname = u'%s\x1b[36m%s\x1b[0m' % (name, ext)
...@@ -88,16 +93,35 @@ class Command(object): ...@@ -88,16 +93,35 @@ class Command(object):
return text return text
return u'\x1b[2m%s\x1b[0m' % 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): def wrap_table_header(self, name):
if not self.terminal_colors: if not self.terminal_colors:
return name return name
return '\x1b[1;32m%s\x1b[0m' % 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: 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: except OpenerError, e:
self.error(str(e)+'\n') self.error(str(e), '\n')
sys.exit(1) sys.exit(1)
fs.cache_hint(True) fs.cache_hint(True)
return fs, path return fs, path
...@@ -168,12 +192,15 @@ class Command(object): ...@@ -168,12 +192,15 @@ class Command(object):
return text return text
def output(self, msg, verbose=False):
def output(self, msgs, verbose=False):
if verbose and not self.verbose: if verbose and not self.verbose:
return return
if isinstance(msgs, basestring):
msgs = (msgs,)
for msg in msgs:
self.output_file.write(self.text_encode(msg)) self.output_file.write(self.text_encode(msg))
def output_table(self, table, col_process=None, verbose=False): def output_table(self, table, col_process=None, verbose=False):
if verbose and not self.verbose: if verbose and not self.verbose:
...@@ -199,7 +226,8 @@ class Command(object): ...@@ -199,7 +226,8 @@ class Command(object):
lines.append(self.text_encode('%s\n' % ' '.join(out_col).rstrip())) lines.append(self.text_encode('%s\n' % ' '.join(out_col).rstrip()))
self.output(''.join(lines)) self.output(''.join(lines))
def error(self, msg): def error(self, *msgs):
for msg in msgs:
self.error_file.write('%s: %s' % (self.name, self.text_encode(msg))) self.error_file.write('%s: %s' % (self.name, self.text_encode(msg)))
def get_optparse(self): def get_optparse(self):
...@@ -230,22 +258,22 @@ class Command(object): ...@@ -230,22 +258,22 @@ class Command(object):
words = [] words = []
line_len = 0 line_len = 0
for word in line.split(): for word in line.split():
if word == '*':
word = ' *'
if line_len + len(word) > self.terminal_width: if line_len + len(word) > self.terminal_width:
self.output(' '.join(words)) self.output((self.highlight_fsurls(' '.join(words)), '\n'))
self.output('\n')
del words[:] del words[:]
line_len = 0 line_len = 0
words.append(word) words.append(word)
line_len += len(word) + 1 line_len += len(word) + 1
if words: if words:
self.output(' '.join(words)) self.output(self.highlight_fsurls(' '.join(words)))
self.output('\n') self.output('\n')
for names, desc in opener_table: for names, desc in opener_table:
self.output('\n') self.output(('-' * self.terminal_width, '\n'))
proto = ', '.join([n+'://' for n in names]) proto = ', '.join([n+'://' for n in names])
self.output(self.wrap_dirname('[%s]' % proto)) self.output((self.wrap_dirname('[%s]' % proto), '\n\n'))
self.output('\n')
if not desc.strip(): if not desc.strip():
desc = "No information available" desc = "No information available"
wrap_line(desc) wrap_line(desc)
...@@ -282,7 +310,6 @@ class Command(object): ...@@ -282,7 +310,6 @@ class Command(object):
return 1 return 1
if __name__ == "__main__": if __name__ == "__main__":
command = Command() command = Command()
sys.exit(command.run()) sys.exit(command.run())
\ No newline at end of file
...@@ -1035,6 +1035,15 @@ class FTPFS(FS): ...@@ -1035,6 +1035,15 @@ class FTPFS(FS):
pass pass
self.closed = True 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 @ftperrors
def open(self, path, mode='r'): def open(self, path, mode='r'):
mode = mode.lower() mode = mode.lower()
...@@ -1249,6 +1258,9 @@ class FTPFS(FS): ...@@ -1249,6 +1258,9 @@ class FTPFS(FS):
@ftperrors @ftperrors
def desc(self, path): def desc(self, path):
url = self.getpathurl(path, allow_none=True)
if url:
return url
dirlist, fname = self._check_path(path) dirlist, fname = self._check_path(path)
if fname not in dirlist: if fname not in dirlist:
raise ResourceNotFoundError(path) raise ResourceNotFoundError(path)
......
...@@ -210,7 +210,13 @@ class Opener(object): ...@@ -210,7 +210,13 @@ class Opener(object):
class OSFSOpener(Opener): class OSFSOpener(Opener):
names = ['osfs', 'file'] 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 @classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir): def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
...@@ -226,7 +232,12 @@ class OSFSOpener(Opener): ...@@ -226,7 +232,12 @@ class OSFSOpener(Opener):
class ZipOpener(Opener): class ZipOpener(Opener):
names = ['zip', 'zip64'] 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 @classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir): def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
...@@ -263,7 +274,11 @@ class ZipOpener(Opener): ...@@ -263,7 +274,11 @@ class ZipOpener(Opener):
class RPCOpener(Opener): class RPCOpener(Opener):
names = ['rpc'] 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 @classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir): def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
...@@ -284,7 +299,11 @@ class RPCOpener(Opener): ...@@ -284,7 +299,11 @@ class RPCOpener(Opener):
class FTPOpener(Opener): class FTPOpener(Opener):
names = ['ftp'] 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 @classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir): def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
...@@ -316,7 +335,11 @@ class FTPOpener(Opener): ...@@ -316,7 +335,11 @@ class FTPOpener(Opener):
class SFTPOpener(Opener): class SFTPOpener(Opener):
names = ['sftp'] 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 @classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir): def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
...@@ -367,7 +390,13 @@ class SFTPOpener(Opener): ...@@ -367,7 +390,13 @@ class SFTPOpener(Opener):
class MemOpener(Opener): class MemOpener(Opener):
names = ['mem', 'ram'] 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 @classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir): def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
...@@ -380,7 +409,10 @@ class MemOpener(Opener): ...@@ -380,7 +409,10 @@ class MemOpener(Opener):
class DebugOpener(Opener): class DebugOpener(Opener):
names = ['debug'] 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 @classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir): def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
...@@ -398,7 +430,12 @@ class DebugOpener(Opener): ...@@ -398,7 +430,12 @@ class DebugOpener(Opener):
class TempOpener(Opener): class TempOpener(Opener):
names = ['temp'] 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 @classmethod
def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir): def get_fs(cls, registry, fs_name, fs_name_params, fs_path, writeable, create_dir):
......
...@@ -93,6 +93,7 @@ class SFTPFS(FS): ...@@ -93,6 +93,7 @@ class SFTPFS(FS):
credentials = dict(username=username, credentials = dict(username=username,
password=password, password=password,
pkey=pkey) pkey=pkey)
self.credentials = credentials
if encoding is None: if encoding is None:
encoding = "utf8" encoding = "utf8"
...@@ -104,6 +105,11 @@ class SFTPFS(FS): ...@@ -104,6 +105,11 @@ class SFTPFS(FS):
self._transport = None self._transport = None
self._client = 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__() super(SFTPFS, self).__init__()
self.root_path = abspath(normpath(root_path)) self.root_path = abspath(normpath(root_path))
...@@ -124,8 +130,7 @@ class SFTPFS(FS): ...@@ -124,8 +130,7 @@ class SFTPFS(FS):
connection.start_client() connection.start_client()
if (password or pkey) and not connection.is_authenticated(): if not connection.is_authenticated():
try: try:
if pkey: if pkey:
connection.auth_publickey(username, pkey) connection.auth_publickey(username, pkey)
...@@ -146,6 +151,8 @@ class SFTPFS(FS): ...@@ -146,6 +151,8 @@ class SFTPFS(FS):
self._transport = connection self._transport = connection
def __unicode__(self):
return '<SFTPFS: %s>' % self.desc('/')
@classmethod @classmethod
def _agent_auth(cls, transport, username): def _agent_auth(cls, transport, username):
...@@ -213,6 +220,21 @@ class SFTPFS(FS): ...@@ -213,6 +220,21 @@ class SFTPFS(FS):
raise PathError(path,msg="Path is outside root: %(path)s") raise PathError(path,msg="Path is outside root: %(path)s")
return npath 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 @convert_os_errors
def open(self,path,mode="rb",bufsize=-1): def open(self,path,mode="rb",bufsize=-1):
npath = self._normpath(path) npath = self._normpath(path)
...@@ -234,8 +256,11 @@ class SFTPFS(FS): ...@@ -234,8 +256,11 @@ class SFTPFS(FS):
def desc(self, path): def desc(self, path):
npath = self._normpath(path) npath = self._normpath(path)
if self.hostname:
return u'sftp://%s%s' % (self.hostname, path)
else:
addr, port = self._transport.getpeername() addr, port = self._transport.getpeername()
return u'%s on sftp://%s:%i' % (self.client.normalize(npath), addr, port) return u'sftp://%s:%i%s' % (addr, port, self.client.normalize(npath))
@convert_os_errors @convert_os_errors
def exists(self,path): def exists(self,path):
...@@ -282,10 +307,9 @@ class SFTPFS(FS): ...@@ -282,10 +307,9 @@ class SFTPFS(FS):
if dirs_only or files_only: if dirs_only or files_only:
attrs = self.client.listdir_attr(npath) attrs = self.client.listdir_attr(npath)
attrs_map = dict((a.filename, a) for a in attrs) attrs_map = dict((a.filename, a) for a in attrs)
paths = attrs_map.keys() paths = list(attrs_map.iterkeys())
else: else:
paths = self.client.listdir(npath) paths = self.client.listdir(npath)
except IOError, e: except IOError, e:
if getattr(e,"errno",None) == 2: if getattr(e,"errno",None) == 2:
if self.isfile(path): if self.isfile(path):
...@@ -298,20 +322,21 @@ class SFTPFS(FS): ...@@ -298,20 +322,21 @@ class SFTPFS(FS):
if attrs_map: if attrs_map:
if dirs_only: if dirs_only:
filter_paths = [] filter_paths = []
for path, attr in attrs_map.iteritems(): for apath, attr in attrs_map.iteritems():
if isdir(self, path, attr.__dict__): if isdir(self, path, attr.__dict__):
filter_paths.append(path) filter_paths.append(apath)
paths = filter_paths paths = filter_paths
elif files_only: elif files_only:
filter_paths = [] filter_paths = []
for path, attr in attrs_map.iteritems(): for apath, attr in attrs_map.iteritems():
if isfile(self, path, attr.__dict__): if isfile(self, apath, attr.__dict__):
filter_paths.append(path) filter_paths.append(apath)
paths = filter_paths paths = filter_paths
for (i,p) in enumerate(paths): for (i,p) in enumerate(paths):
if not isinstance(p,unicode): 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) return self._listdir_helper(path, paths, wildcard, full, absolute, False, False)
...@@ -477,7 +502,7 @@ class SFTPFS(FS): ...@@ -477,7 +502,7 @@ class SFTPFS(FS):
@classmethod @classmethod
def _extract_info(cls, stats): def _extract_info(cls, stats):
fromtimestamp = datetime.datetime.fromtimestamp 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'] info['size'] = info['st_size']
ct = info.get('st_ctime') ct = info.get('st_ctime')
if ct is not None: if ct is not None:
...@@ -494,7 +519,7 @@ class SFTPFS(FS): ...@@ -494,7 +519,7 @@ class SFTPFS(FS):
def getinfo(self, path): def getinfo(self, path):
npath = self._normpath(path) npath = self._normpath(path)
stats = self.client.stat(npath) 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'] info['size'] = info['st_size']
ct = info.get('st_ctime', None) ct = info.get('st_ctime', None)
if ct is not None: if ct is not None:
......
...@@ -30,18 +30,16 @@ class SubFS(WrapFS): ...@@ -30,18 +30,16 @@ class SubFS(WrapFS):
return abspath(normpath(path))[len(self.sub_dir):] return abspath(normpath(path))[len(self.sub_dir):]
def __str__(self): def __str__(self):
return "%s/%s" % (self.wrapped_fs, self.sub_dir.lstrip('/')) return self.wrapped_fs.desc(self.sub_dir)
def __unicode__(self): 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): def __repr__(self):
return str(self) return str(self)
def desc(self, path): def desc(self, path):
#return self.wrapped_fs.desc(join(self.sub_dir, path)) return '%s!%s' % (self.wrapped_fs.desc(self.sub_dir), path)
desc = "%s!%s" % (str(self), path)
return desc
def setcontents(self, path, data, chunk_size=64*1024): def setcontents(self, path, data, chunk_size=64*1024):
path = self._encode(path) path = self._encode(path)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment