Commit e69d8929 by willmcgugan

Command improvements

parent a143bd9a
......@@ -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()
......
......@@ -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')
......
......@@ -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()
......
......@@ -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()
......
......@@ -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))
......
......@@ -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):
......
......@@ -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()
......
......@@ -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()
......
......@@ -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)
......
......@@ -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):
......
......@@ -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:
......
......@@ -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)
......
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