summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog1
-rw-r--r--fs/__init__.py3
-rw-r--r--fs/base.py307
-rw-r--r--fs/errors.py5
-rw-r--r--fs/ftpfs.py7
-rw-r--r--fs/memoryfs.py10
-rw-r--r--fs/mountfs.py2
-rw-r--r--fs/multifs.py4
-rw-r--r--fs/osfs/__init__.py9
-rw-r--r--fs/osfs/watch.py2
-rw-r--r--fs/osfs/xattrs.py8
-rw-r--r--fs/s3fs.py2
-rw-r--r--fs/sftpfs.py4
-rw-r--r--fs/tests/__init__.py4
-rw-r--r--fs/tests/test_expose.py1
-rw-r--r--fs/tests/test_utils.py106
-rw-r--r--fs/utils.py76
-rw-r--r--fs/wrapfs/__init__.py7
-rw-r--r--fs/wrapfs/lazyfs.py2
-rw-r--r--fs/wrapfs/subfs.py53
20 files changed, 394 insertions, 219 deletions
diff --git a/ChangeLog b/ChangeLog
index 0e72bac..d9e4fdc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -83,3 +83,4 @@
0.5:
* Ported to Python 3.X
+ * Added a DeleteRootError to exceptions thrown when trying to delete '/'
diff --git a/fs/__init__.py b/fs/__init__.py
index 7f66c70..2d5f96b 100644
--- a/fs/__init__.py
+++ b/fs/__init__.py
@@ -18,9 +18,6 @@ implementations of this interface such as:
__version__ = "0.4.1"
__author__ = "Will McGugan (will@willmcgugan.com)"
-# No longer necessary - WM
-#from base import *
-
# provide these by default so people can use 'fs.path.basename' etc.
import errors
import path
diff --git a/fs/base.py b/fs/base.py
index f7268f6..9947713 100644
--- a/fs/base.py
+++ b/fs/base.py
@@ -58,9 +58,9 @@ class DummyLock(object):
pass
def __enter__(self):
- pass
+ return self
- def __exit__(self, *args):
+ def __exit__(self, exc_type, exc_value, traceback):
pass
@@ -154,7 +154,7 @@ class FS(object):
_meta = {}
- def __init__(self, thread_synchronize=False):
+ def __init__(self, thread_synchronize=True):
"""The base class for Filesystem objects.
:param thread_synconize: If True, a lock object will be created for the object, otherwise a dummy lock will be used.
@@ -656,20 +656,21 @@ class FS(object):
:type modified_time: datetime
"""
-
- sys_path = self.getsyspath(path, allow_none=True)
- if sys_path is not None:
- now = datetime.datetime.now()
- if accessed_time is None:
- accessed_time = now
- if modified_time is None:
- modified_time = now
- accessed_time = int(time.mktime(accessed_time.timetuple()))
- modified_time = int(time.mktime(modified_time.timetuple()))
- os.utime(sys_path, (accessed_time, modified_time))
- return True
- else:
- raise UnsupportedError("settimes")
+
+ with self._lock:
+ sys_path = self.getsyspath(path, allow_none=True)
+ if sys_path is not None:
+ now = datetime.datetime.now()
+ if accessed_time is None:
+ accessed_time = now
+ if modified_time is None:
+ modified_time = now
+ accessed_time = int(time.mktime(accessed_time.timetuple()))
+ modified_time = int(time.mktime(modified_time.timetuple()))
+ os.utime(sys_path, (accessed_time, modified_time))
+ return True
+ else:
+ raise UnsupportedError("settimes")
def getinfo(self, path):
"""Returns information for a path as a dictionary. The exact content of
@@ -1068,30 +1069,30 @@ class FS(object):
:type chunk_size: bool
"""
-
- if not self.isfile(src):
- if self.isdir(src):
- raise ResourceInvalidError(src, msg="Source is not a file: %(path)s")
- raise ResourceNotFoundError(src)
- if not overwrite and self.exists(dst):
- raise DestinationExistsError(dst)
-
- src_syspath = self.getsyspath(src, allow_none=True)
- dst_syspath = self.getsyspath(dst, allow_none=True)
-
- if src_syspath is not None and dst_syspath is not None:
- self._shutil_copyfile(src_syspath, dst_syspath)
- else:
- src_file = None
- try:
- src_file = self.open(src, "rb")
- self.setcontents(dst, src_file, chunk_size=chunk_size)
- except ResourceNotFoundError:
- if self.exists(src) and not self.exists(dirname(dst)):
- raise ParentDirectoryMissingError(dst)
- finally:
- if src_file is not None:
- src_file.close()
+ with self._lock:
+ if not self.isfile(src):
+ if self.isdir(src):
+ raise ResourceInvalidError(src, msg="Source is not a file: %(path)s")
+ raise ResourceNotFoundError(src)
+ if not overwrite and self.exists(dst):
+ raise DestinationExistsError(dst)
+
+ src_syspath = self.getsyspath(src, allow_none=True)
+ dst_syspath = self.getsyspath(dst, allow_none=True)
+
+ if src_syspath is not None and dst_syspath is not None:
+ self._shutil_copyfile(src_syspath, dst_syspath)
+ else:
+ src_file = None
+ try:
+ src_file = self.open(src, "rb")
+ self.setcontents(dst, src_file, chunk_size=chunk_size)
+ except ResourceNotFoundError:
+ if self.exists(src) and not self.exists(dirname(dst)):
+ raise ParentDirectoryMissingError(dst)
+ finally:
+ if src_file is not None:
+ src_file.close()
@classmethod
@convert_os_errors
@@ -1129,25 +1130,26 @@ class FS(object):
"""
- src_syspath = self.getsyspath(src, allow_none=True)
- dst_syspath = self.getsyspath(dst, allow_none=True)
-
- # Try to do an os-level rename if possible.
- # Otherwise, fall back to copy-and-remove.
- if src_syspath is not None and dst_syspath is not None:
- if not os.path.isfile(src_syspath):
- if os.path.isdir(src_syspath):
- raise ResourceInvalidError(src, msg="Source is not a file: %(path)s")
- raise ResourceNotFoundError(src)
- if not overwrite and os.path.exists(dst_syspath):
- raise DestinationExistsError(dst)
- try:
- os.rename(src_syspath, dst_syspath)
- return
- except OSError:
- pass
- self.copy(src, dst, overwrite=overwrite, chunk_size=chunk_size)
- self.remove(src)
+ with self._lock:
+ src_syspath = self.getsyspath(src, allow_none=True)
+ dst_syspath = self.getsyspath(dst, allow_none=True)
+
+ # Try to do an os-level rename if possible.
+ # Otherwise, fall back to copy-and-remove.
+ if src_syspath is not None and dst_syspath is not None:
+ if not os.path.isfile(src_syspath):
+ if os.path.isdir(src_syspath):
+ raise ResourceInvalidError(src, msg="Source is not a file: %(path)s")
+ raise ResourceNotFoundError(src)
+ if not overwrite and os.path.exists(dst_syspath):
+ raise DestinationExistsError(dst)
+ try:
+ os.rename(src_syspath, dst_syspath)
+ return
+ except OSError:
+ pass
+ self.copy(src, dst, overwrite=overwrite, chunk_size=chunk_size)
+ self.remove(src)
def movedir(self, src, dst, overwrite=False, ignore_errors=False, chunk_size=16384):
"""moves a directory from one location to another.
@@ -1169,53 +1171,54 @@ class FS(object):
:raise `fs.errors.DestinationExistsError`: if destination exists and `overwrite` is False
"""
- if not self.isdir(src):
- if self.isfile(src):
- raise ResourceInvalidError(src, msg="Source is not a directory: %(path)s")
- raise ResourceNotFoundError(src)
- if not overwrite and self.exists(dst):
- raise DestinationExistsError(dst)
-
- src_syspath = self.getsyspath(src, allow_none=True)
- dst_syspath = self.getsyspath(dst, allow_none=True)
-
- if src_syspath is not None and dst_syspath is not None:
- try:
- os.rename(src_syspath, dst_syspath)
- return
- except OSError:
- pass
-
- def movefile_noerrors(src, dst, **kwargs):
- try:
- return self.move(src, dst, **kwargs)
- except FSError:
- return
- if ignore_errors:
- movefile = movefile_noerrors
- else:
- movefile = self.move
-
- src = abspath(src)
- dst = abspath(dst)
-
- if dst:
- self.makedir(dst, allow_recreate=overwrite)
-
- for dirname, filenames in self.walk(src, search="depth"):
-
- dst_dirname = relpath(frombase(src, abspath(dirname)))
- dst_dirpath = pathjoin(dst, dst_dirname)
- self.makedir(dst_dirpath, allow_recreate=True, recursive=True)
-
- for filename in filenames:
-
- src_filename = pathjoin(dirname, filename)
- dst_filename = pathjoin(dst_dirpath, filename)
- movefile(src_filename, dst_filename, overwrite=overwrite, chunk_size=chunk_size)
-
- self.removedir(dirname)
-
+ with self._lock:
+ if not self.isdir(src):
+ if self.isfile(src):
+ raise ResourceInvalidError(src, msg="Source is not a directory: %(path)s")
+ raise ResourceNotFoundError(src)
+ if not overwrite and self.exists(dst):
+ raise DestinationExistsError(dst)
+
+ src_syspath = self.getsyspath(src, allow_none=True)
+ dst_syspath = self.getsyspath(dst, allow_none=True)
+
+ if src_syspath is not None and dst_syspath is not None:
+ try:
+ os.rename(src_syspath, dst_syspath)
+ return
+ except OSError:
+ pass
+
+ def movefile_noerrors(src, dst, **kwargs):
+ try:
+ return self.move(src, dst, **kwargs)
+ except FSError:
+ return
+ if ignore_errors:
+ movefile = movefile_noerrors
+ else:
+ movefile = self.move
+
+ src = abspath(src)
+ dst = abspath(dst)
+
+ if dst:
+ self.makedir(dst, allow_recreate=overwrite)
+
+ for dirname, filenames in self.walk(src, search="depth"):
+
+ dst_dirname = relpath(frombase(src, abspath(dirname)))
+ dst_dirpath = pathjoin(dst, dst_dirname)
+ self.makedir(dst_dirpath, allow_recreate=True, recursive=True)
+
+ for filename in filenames:
+
+ src_filename = pathjoin(dirname, filename)
+ dst_filename = pathjoin(dst_dirpath, filename)
+ movefile(src_filename, dst_filename, overwrite=overwrite, chunk_size=chunk_size)
+
+ self.removedir(dirname)
+
def copydir(self, src, dst, overwrite=False, ignore_errors=False, chunk_size=16384):
"""copies a directory from one location to another.
@@ -1232,38 +1235,39 @@ class FS(object):
is required (defaults to 16K)
"""
- if not self.isdir(src):
- raise ResourceInvalidError(src, msg="Source is not a directory: %(path)s")
- def copyfile_noerrors(src, dst, **kwargs):
- try:
- return self.copy(src, dst, **kwargs)
- except FSError:
- return
- if ignore_errors:
- copyfile = copyfile_noerrors
- else:
- copyfile = self.copy
-
- src = abspath(src)
- dst = abspath(dst)
-
- if not overwrite and self.exists(dst):
- raise DestinationExistsError(dst)
-
- if dst:
- self.makedir(dst, allow_recreate=overwrite)
-
- for dirname, filenames in self.walk(src):
-
- dst_dirname = relpath(frombase(src, abspath(dirname)))
- dst_dirpath = pathjoin(dst, dst_dirname)
- self.makedir(dst_dirpath, allow_recreate=True, recursive=True)
-
- for filename in filenames:
-
- src_filename = pathjoin(dirname, filename)
- dst_filename = pathjoin(dst_dirpath, filename)
- copyfile(src_filename, dst_filename, overwrite=overwrite, chunk_size=chunk_size)
+ with self._lock:
+ if not self.isdir(src):
+ raise ResourceInvalidError(src, msg="Source is not a directory: %(path)s")
+ def copyfile_noerrors(src, dst, **kwargs):
+ try:
+ return self.copy(src, dst, **kwargs)
+ except FSError:
+ return
+ if ignore_errors:
+ copyfile = copyfile_noerrors
+ else:
+ copyfile = self.copy
+
+ src = abspath(src)
+ dst = abspath(dst)
+
+ if not overwrite and self.exists(dst):
+ raise DestinationExistsError(dst)
+
+ if dst:
+ self.makedir(dst, allow_recreate=True)
+
+ for dirname, filenames in self.walk(src):
+
+ dst_dirname = relpath(frombase(src, abspath(dirname)))
+ dst_dirpath = pathjoin(dst, dst_dirname)
+ self.makedir(dst_dirpath, allow_recreate=True, recursive=True)
+
+ for filename in filenames:
+
+ src_filename = pathjoin(dirname, filename)
+ dst_filename = pathjoin(dst_dirpath, filename)
+ copyfile(src_filename, dst_filename, overwrite=overwrite, chunk_size=chunk_size)
def isdirempty(self, path):
"""Check if a directory is empty (contains no files or sub-directories)
@@ -1273,13 +1277,14 @@ class FS(object):
:rtype: bool
"""
- path = normpath(path)
- iter_dir = iter(self.listdir(path))
- try:
- iter_dir.next()
- except StopIteration:
- return True
- return False
+ with self._lock:
+ path = normpath(path)
+ iter_dir = iter(self.listdir(path))
+ try:
+ iter_dir.next()
+ except StopIteration:
+ return True
+ return False
def makeopendir(self, path, recursive=False):
"""makes a directory (if it doesn't exist) and returns an FS object for
@@ -1291,11 +1296,11 @@ class FS(object):
:return: the opened dir
:rtype: an FS object
- """
-
- self.makedir(path, allow_recreate=True, recursive=recursive)
- dir_fs = self.opendir(path)
- return dir_fs
+ """
+ with self._lock:
+ self.makedir(path, allow_recreate=True, recursive=recursive)
+ dir_fs = self.opendir(path)
+ return dir_fs
def printtree(self, max_levels=5):
"""Prints a tree structure of the FS object to the console
diff --git a/fs/errors.py b/fs/errors.py
index ba8035e..f066926 100644
--- a/fs/errors.py
+++ b/fs/errors.py
@@ -18,6 +18,7 @@ __all__ = ['FSError',
'PermissionDeniedError',
'FSClosedError',
'OperationTimeoutError',
+ 'DeleteRootError',
'ResourceError',
'NoSysPathError',
'NoMetaError',
@@ -119,6 +120,10 @@ class OperationTimeoutError(OperationFailedError):
default_message = "Unable to %(opname)s: operation timed out"
+class DeleteRootError(OperationFailedError):
+ default_message = "Can't delete root dir"
+
+
class ResourceError(FSError):
"""Base exception class for error associated with a specific resource."""
default_message = "Unspecified resource error: %(path)s"
diff --git a/fs/ftpfs.py b/fs/ftpfs.py
index bba8333..aebe1d5 100644
--- a/fs/ftpfs.py
+++ b/fs/ftpfs.py
@@ -1294,11 +1294,13 @@ class FTPFS(FS):
@ftperrors
def removedir(self, path, recursive=False, force=False):
- path = abspath(normpath(path))
+ path = abspath(normpath(path))
if not self.exists(path):
raise ResourceNotFoundError(path)
if self.isfile(path):
raise ResourceInvalidError(path)
+ if normpath(path) in ('', '/'):
+ raise DeleteRootError(path)
if not force:
for _checkpath in self.listdir(path):
@@ -1319,7 +1321,8 @@ class FTPFS(FS):
pass
if recursive:
try:
- self.removedir(dirname(path), recursive=True)
+ if dirname(path) not in ('', '/'):
+ self.removedir(dirname(path), recursive=True)
except DirectoryNotEmptyError:
pass
self.clear_dircache(dirname(path), path)
diff --git a/fs/memoryfs.py b/fs/memoryfs.py
index b03745e..8dd9a89 100644
--- a/fs/memoryfs.py
+++ b/fs/memoryfs.py
@@ -451,6 +451,8 @@ class MemoryFS(FS):
@synchronize
def removedir(self, path, recursive=False, force=False):
path = normpath(path)
+ if path in ('', '/'):
+ raise DeleteRootError(path)
dir_entry = self._get_dir_entry(path)
if dir_entry is None:
@@ -466,10 +468,14 @@ class MemoryFS(FS):
while rpathname:
rpathname, dirname = pathsplit(rpathname)
parent_dir = self._get_dir_entry(rpathname)
+ if not dirname:
+ raise DeleteRootError(path)
del parent_dir.contents[dirname]
else:
pathname, dirname = pathsplit(path)
parent_dir = self._get_dir_entry(pathname)
+ if not dirname:
+ raise DeleteRootError(path)
del parent_dir.contents[dirname]
@synchronize
@@ -581,7 +587,7 @@ class MemoryFS(FS):
super(MemoryFS, self).movedir(src, dst, overwrite, ignore_errors=ignore_errors, chunk_size=chunk_size)
dst_dir_entry = self._get_dir_entry(dst)
if dst_dir_entry is not None:
- dst_dir_entry.xattrs.update(src_xattrs)
+ dst_dir_entry.xattrs.update(src_xattrs)
@synchronize
def copy(self, src, dst, overwrite=False, chunk_size=1024*64):
@@ -589,7 +595,7 @@ class MemoryFS(FS):
if src_dir_entry is None:
raise ResourceNotFoundError(src)
src_xattrs = src_dir_entry.xattrs.copy()
- super(MemoryFS, self).copy(src, dst, overwrite, chunk_size)
+ super(MemoryFS, self).copy(src, dst, overwrite, chunk_size)
dst_dir_entry = self._get_dir_entry(dst)
if dst_dir_entry is not None:
dst_dir_entry.xattrs.update(src_xattrs)
diff --git a/fs/mountfs.py b/fs/mountfs.py
index 684ce7a..5a63a5c 100644
--- a/fs/mountfs.py
+++ b/fs/mountfs.py
@@ -331,6 +331,8 @@ class MountFS(FS):
@synchronize
def removedir(self, path, recursive=False, force=False):
path = normpath(path)
+ if path in ('', '/'):
+ raise DeleteRootError(path)
fs, _mount_path, delegate_path = self._delegate(path)
if fs is self or fs is None:
raise ResourceInvalidError(path, msg="Can not removedir for an un-mounted path")
diff --git a/fs/multifs.py b/fs/multifs.py
index f960dd6..cd4e987 100644
--- a/fs/multifs.py
+++ b/fs/multifs.py
@@ -292,7 +292,9 @@ class MultiFS(FS):
@synchronize
def removedir(self, path, recursive=False, force=False):
if self.writefs is None:
- raise OperationFailedError('removedir', path=path, msg="No writeable FS set")
+ raise OperationFailedError('removedir', path=path, msg="No writeable FS set")
+ if normpath(path) in ('', '/'):
+ raise DeleteRootError(path)
self.writefs.removedir(path, recursive=recursive, force=force)
@synchronize
diff --git a/fs/osfs/__init__.py b/fs/osfs/__init__.py
index f491e22..936e5c0 100644
--- a/fs/osfs/__init__.py
+++ b/fs/osfs/__init__.py
@@ -272,7 +272,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
raise
@convert_os_errors
- def removedir(self, path, recursive=False, force=False):
+ def removedir(self, path, recursive=False, force=False):
sys_path = self.getsyspath(path)
if force:
for path2 in self.listdir(path, absolute=True, files_only=True):
@@ -286,14 +286,15 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
except ResourceNotFoundError:
pass
# Don't remove the root directory of this FS
- if path in ("","/"):
- return
+ if path in ('', '/'):
+ raise DeleteRootError(path)
os.rmdir(sys_path)
# Using os.removedirs() for this can result in dirs being
# removed outside the root of this FS, so we recurse manually.
if recursive:
try:
- self.removedir(dirname(path),recursive=True)
+ if dirname(path) not in ('', '/'):
+ self.removedir(dirname(path),recursive=True)
except DirectoryNotEmptyError:
pass
diff --git a/fs/osfs/watch.py b/fs/osfs/watch.py
index dc5a13d..ff1f71d 100644
--- a/fs/osfs/watch.py
+++ b/fs/osfs/watch.py
@@ -34,6 +34,8 @@ if OSFSWatchMixin is None:
# Fall back to raising UnsupportedError
if OSFSWatchMixin is None:
class OSFSWatchMixin(object):
+ def __init__(self, *args, **kwargs):
+ super(OSFSWatchMixin, self).__init__(*args, **kwargs)
def add_watcher(self,*args,**kwds):
raise UnsupportedError
def del_watcher(self,watcher_or_callback):
diff --git a/fs/osfs/xattrs.py b/fs/osfs/xattrs.py
index 68814e4..2ee83a5 100644
--- a/fs/osfs/xattrs.py
+++ b/fs/osfs/xattrs.py
@@ -23,9 +23,12 @@ except ImportError:
if xattr is not None:
- class OSFSXAttrMixin(FS):
+ class OSFSXAttrMixin(object):
"""Mixin providing extended-attribute support via the 'xattr' module"""
+ def __init__(self, *args, **kwargs):
+ super(OSFSXAttrMixin, self).__init__(*args, **kwargs)
+
@convert_os_errors
def setxattr(self, path, key, value):
xattr.xattr(self.getsyspath(path))[key]=value
@@ -53,6 +56,9 @@ else:
class OSFSXAttrMixin(object):
"""Mixin disable extended-attribute support."""
+ def __init__(self, *args, **kwargs):
+ super(OSFSXAttrMixin, self).__init__(*args, **kwargs)
+
def getxattr(self,path,key,default=None):
raise UnsupportedError
diff --git a/fs/s3fs.py b/fs/s3fs.py
index 80f3f08..031f5d6 100644
--- a/fs/s3fs.py
+++ b/fs/s3fs.py
@@ -496,6 +496,8 @@ class S3FS(FS):
def removedir(self,path,recursive=False,force=False):
"""Remove the directory at the given path."""
+ if normpath(path) in ('', '/'):
+ raise DeleteRootError(path)
s3path = self._s3path(path)
if s3path != self._prefix:
s3path = s3path + self._separator
diff --git a/fs/sftpfs.py b/fs/sftpfs.py
index 22dfc4b..3b085ed 100644
--- a/fs/sftpfs.py
+++ b/fs/sftpfs.py
@@ -475,8 +475,8 @@ class SFTPFS(FS):
@convert_os_errors
def removedir(self,path,recursive=False,force=False):
npath = self._normpath(path)
- if path in ("","/"):
- return
+ if normpath(path) in ('', '/'):
+ raise DeleteRootError(path)
if force:
for path2 in self.listdir(path,absolute=True):
try:
diff --git a/fs/tests/__init__.py b/fs/tests/__init__.py
index 91e6a03..79da71a 100644
--- a/fs/tests/__init__.py
+++ b/fs/tests/__init__.py
@@ -840,6 +840,8 @@ class FSTestCases(object):
self.assertTrue(cmp_datetimes(d1, info['accessed_time']))
self.assertTrue(cmp_datetimes(d2, info['modified_time']))
+ def test_removeroot(self):
+ self.assertRaises(DeleteRootError, self.fs.removedir, "/")
# May be disabled - see end of file
class ThreadingTestCases(object):
@@ -1023,7 +1025,7 @@ class ThreadingTestCases(object):
self.fs.copydir("a","copy of a")
def copydir_overwrite():
self._yield()
- self.fs.copydir("a","copy of a",overwrite=True)
+ self.fs.copydir("a","copy of a",overwrite=True)
# This should error out since we're not overwriting
self.assertRaises(DestinationExistsError,self._runThreads,copydir,copydir)
# This should run to completion and give a valid state, unless
diff --git a/fs/tests/test_expose.py b/fs/tests/test_expose.py
index 8a3e5a6..caa2f2c 100644
--- a/fs/tests/test_expose.py
+++ b/fs/tests/test_expose.py
@@ -127,6 +127,7 @@ except ImportError:
class TestSFTPFS(TestRPCFS):
__test__ = not PY3
+ __test__ = False
def makeServer(self,fs,addr):
return BaseSFTPServer(addr,fs)
diff --git a/fs/tests/test_utils.py b/fs/tests/test_utils.py
new file mode 100644
index 0000000..b634029
--- /dev/null
+++ b/fs/tests/test_utils.py
@@ -0,0 +1,106 @@
+import unittest
+
+from fs.tempfs import TempFS
+from fs.memoryfs import MemoryFS
+from fs import utils
+
+class TestUtils(unittest.TestCase):
+
+ def _make_fs(self, fs):
+ fs.setcontents("f1", "file 1")
+ fs.setcontents("f2", "file 2")
+ fs.setcontents("f3", "file 3")
+ fs.makedir("foo/bar", recursive=True)
+ fs.setcontents("foo/bar/fruit", "apple")
+
+ def _check_fs(self, fs):
+ self.assert_(fs.isfile("f1"))
+ self.assert_(fs.isfile("f2"))
+ self.assert_(fs.isfile("f3"))
+ self.assert_(fs.isdir("foo/bar"))
+ self.assert_(fs.isfile("foo/bar/fruit"))
+ self.assertEqual(fs.getcontents("f1", "rb"), "file 1")
+ self.assertEqual(fs.getcontents("f2", "rb"), "file 2")
+ self.assertEqual(fs.getcontents("f3", "rb"), "file 3")
+ self.assertEqual(fs.getcontents("foo/bar/fruit", "rb"), "apple")
+
+ def test_copydir_root(self):
+ """Test copydir from root"""
+ fs1 = MemoryFS()
+ self._make_fs(fs1)
+ fs2 = MemoryFS()
+ utils.copydir(fs1, fs2)
+ self._check_fs(fs2)
+
+ fs1 = TempFS()
+ self._make_fs(fs1)
+ fs2 = TempFS()
+ utils.copydir(fs1, fs2)
+ self._check_fs(fs2)
+
+ def test_copydir_indir(self):
+ """Test copydir in a directory"""
+ fs1 = MemoryFS()
+ fs2 = MemoryFS()
+ self._make_fs(fs1)
+ utils.copydir(fs1, (fs2, "copy"))
+ self._check_fs(fs2.opendir("copy"))
+
+ fs1 = TempFS()
+ fs2 = TempFS()
+ self._make_fs(fs1)
+ utils.copydir(fs1, (fs2, "copy"))
+ self._check_fs(fs2.opendir("copy"))
+
+ def test_movedir_indir(self):
+ """Test movedir in a directory"""
+ fs1 = MemoryFS()
+ fs2 = MemoryFS()
+ fs1sub = fs1.makeopendir("from")
+ self._make_fs(fs1sub)
+ utils.movedir((fs1, "from"), (fs2, "copy"))
+ self.assert_(not fs1.exists("from"))
+ self._check_fs(fs2.opendir("copy"))
+
+ fs1 = TempFS()
+ fs2 = TempFS()
+ fs1sub = fs1.makeopendir("from")
+ self._make_fs(fs1sub)
+ utils.movedir((fs1, "from"), (fs2, "copy"))
+ self.assert_(not fs1.exists("from"))
+ self._check_fs(fs2.opendir("copy"))
+
+ def test_movedir_root(self):
+ """Test movedir to root dir"""
+ fs1 = MemoryFS()
+ fs2 = MemoryFS()
+ fs1sub = fs1.makeopendir("from")
+ self._make_fs(fs1sub)
+ utils.movedir((fs1, "from"), fs2)
+ self.assert_(not fs1.exists("from"))
+ self._check_fs(fs2)
+
+ fs1 = TempFS()
+ fs2 = TempFS()
+ fs1sub = fs1.makeopendir("from")
+ self._make_fs(fs1sub)
+ utils.movedir((fs1, "from"), fs2)
+ self.assert_(not fs1.exists("from"))
+ self._check_fs(fs2)
+
+if __name__ == "__main__":
+
+ def _make_fs(fs):
+ fs.setcontents("f1", "file 1")
+ fs.setcontents("f2", "file 2")
+ fs.setcontents("f3", "file 3")
+ fs.makedir("foo/bar", recursive=True)
+ fs.setcontents("foo/bar/fruit", "apple")
+
+ fs1 = TempFS()
+ fs2 = TempFS()
+ fs1sub = fs1.makeopendir("from")
+ _make_fs(fs1sub)
+ utils.movedir((fs1, "from"), fs2)
+ #self.assert_(not fs1.exists("from"))
+ #self._check_fs(fs2) \ No newline at end of file
diff --git a/fs/utils.py b/fs/utils.py
index a56dcd0..0dc41d8 100644
--- a/fs/utils.py
+++ b/fs/utils.py
@@ -12,8 +12,7 @@ __all__ = ['copyfile',
'isfile',
'isdir',
'find_duplicates',
- 'print_fs',
- 'wrap_file']
+ 'print_fs']
import os
import sys
@@ -21,7 +20,7 @@ import stat
from fs.mountfs import MountFS
from fs.path import pathjoin
-from fs.errors import DestinationExistsError
+from fs.errors import DestinationExistsError, DeleteRootError
from fs.base import FS
def copyfile(src_fs, src_path, dst_fs, dst_path, overwrite=True, chunk_size=64*1024):
@@ -187,38 +186,51 @@ def movefile_non_atomic(src_fs, src_path, dst_fs, dst_path, overwrite=True, chun
dst.close()
-def movedir(fs1, fs2, overwrite=False, ignore_errors=False, chunk_size=64*1024):
+def movedir(fs1, fs2, create_destination=True, ignore_errors=False, chunk_size=64*1024):
"""Moves contents of a directory from one filesystem to another.
- :param fs1: Source filesystem, or a tuple of (<filesystem>, <directory path>)
+ :param fs1: A tuple of (<filesystem>, <directory path>)
:param fs2: Destination filesystem, or a tuple of (<filesystem>, <directory path>)
+ :param create_destination: If True, the destination will be created if it doesn't exist
:param ignore_errors: If True, exceptions from file moves are ignored
:param chunk_size: Size of chunks to move if a simple copy is used
"""
- if isinstance(fs1, tuple):
- fs1, dir1 = fs1
- fs1 = fs1.opendir(dir1)
+ if not isinstance(fs1, tuple):
+ raise ValueError("first argument must be a tuple of (<filesystem>, <path>)")
+
+ fs1, dir1 = fs1
+ parent_fs1 = fs1
+ parent_dir1 = dir1
+ fs1 = fs1.opendir(dir1)
+
+ print fs1
+ if parent_dir1 in ('', '/'):
+ raise DeleteRootError(dir1)
+
if isinstance(fs2, tuple):
fs2, dir2 = fs2
- fs2.makedir(dir2, allow_recreate=True)
- fs2 = fs2.opendir(dir2)
+ if create_destination:
+ fs2.makedir(dir2, allow_recreate=True, recursive=True)
+ fs2 = fs2.opendir(dir2)
- mount_fs = MountFS()
+ mount_fs = MountFS(auto_close=False)
mount_fs.mount('src', fs1)
mount_fs.mount('dst', fs2)
- mount_fs.movedir('src', 'dst',
- overwrite=overwrite,
+ mount_fs.copydir('src', 'dst',
+ overwrite=True,
ignore_errors=ignore_errors,
- chunk_size=chunk_size)
+ chunk_size=chunk_size)
+ parent_fs1.removedir(parent_dir1, force=True)
-def copydir(fs1, fs2, overwrite=False, ignore_errors=False, chunk_size=64*1024):
+def copydir(fs1, fs2, create_destination=True, ignore_errors=False, chunk_size=64*1024):
"""Copies contents of a directory from one filesystem to another.
:param fs1: Source filesystem, or a tuple of (<filesystem>, <directory path>)
:param fs2: Destination filesystem, or a tuple of (<filesystem>, <directory path>)
+ :param create_destination: If True, the destination will be created if it doesn't exist
:param ignore_errors: If True, exceptions from file moves are ignored
:param chunk_size: Size of chunks to move if a simple copy is used
@@ -228,14 +240,15 @@ def copydir(fs1, fs2, overwrite=False, ignore_errors=False, chunk_size=64*1024):
fs1 = fs1.opendir(dir1)
if isinstance(fs2, tuple):
fs2, dir2 = fs2
- fs2.makedir(dir2, allow_recreate=True)
- fs2 = fs2.opendir(dir2)
+ if create_destination:
+ fs2.makedir(dir2, allow_recreate=True, recursive=True)
+ fs2 = fs2.opendir(dir2)
- mount_fs = MountFS()
+ mount_fs = MountFS(auto_close=False)
mount_fs.mount('src', fs1)
mount_fs.mount('dst', fs2)
- mount_fs.copydir('src', 'dst',
- overwrite=overwrite,
+ mount_fs.copydir('src', 'dst',
+ overwrite=True,
ignore_errors=ignore_errors,
chunk_size=chunk_size)
@@ -519,11 +532,20 @@ def print_fs(fs, path='/', max_levels=5, file_out=None, terminal_colors=None, hi
if __name__ == "__main__":
- from fs.memoryfs import MemoryFS
- m = MemoryFS()
- m.setcontents("test", "Hello, World!" * 10000)
+ from fs.tempfs import TempFS
+ t1 = TempFS()
+ t1.setcontents("foo", "test")
+ t1.makedir("bar")
+ t1.setcontents("bar/baz", "another test")
+
+ t1.tree()
- f = m.open("test")
- print f
- tf = wrap_file(f, "r")
- print repr(tf.read(10))
+ t2 = TempFS()
+ print t2.listdir()
+ movedir(t1, t2)
+
+ print t2.listdir()
+ t1.tree()
+ t2.tree()
+
+
diff --git a/fs/wrapfs/__init__.py b/fs/wrapfs/__init__.py
index 556f522..7b9d1af 100644
--- a/fs/wrapfs/__init__.py
+++ b/fs/wrapfs/__init__.py
@@ -18,6 +18,7 @@ standard unix shell functionality of hiding dot-files in directory listings.
import re
import sys
import fnmatch
+import threading
from fs.base import FS, threading, synchronize, NoDefaultMeta
from fs.errors import *
@@ -65,11 +66,11 @@ class WrapFS(FS):
"""
def __init__(self, fs):
- super(WrapFS,self).__init__()
+ super(WrapFS, self).__init__()
try:
self._lock = fs._lock
- except (AttributeError,FSError):
- self._lock = None
+ except (AttributeError,FSError):
+ self._lock = self._lock = threading.RLock()
self.wrapped_fs = fs
def _file_wrap(self, f, mode):
diff --git a/fs/wrapfs/lazyfs.py b/fs/wrapfs/lazyfs.py
index 02fa1f7..0c1d2e9 100644
--- a/fs/wrapfs/lazyfs.py
+++ b/fs/wrapfs/lazyfs.py
@@ -29,7 +29,7 @@ class LazyFS(WrapFS):
"""
def __init__(self, fs):
- super(LazyFS,self).__init__(fs)
+ super(LazyFS, self).__init__(fs)
self._lazy_creation_lock = Lock()
def __unicode__(self):
diff --git a/fs/wrapfs/subfs.py b/fs/wrapfs/subfs.py
index 79b1e58..1d221a4 100644
--- a/fs/wrapfs/subfs.py
+++ b/fs/wrapfs/subfs.py
@@ -60,27 +60,38 @@ class SubFS(WrapFS):
def removedir(self, path, recursive=False, force=False):
# Careful not to recurse outside the subdir
path = normpath(path)
- if path in ("","/"):
- if not force:
- for path2 in self.listdir(path):
- raise DirectoryNotEmptyError(path)
- else:
- for path2 in self.listdir(path,absolute=True,files_only=True):
- try:
- self.remove(path2)
- except ResourceNotFoundError:
- pass
- for path2 in self.listdir(path,absolute=True,dirs_only=True):
- try:
- self.removedir(path2,force=True)
- except ResourceNotFoundError:
- pass
- else:
- super(SubFS,self).removedir(path,force=force)
- if recursive:
- try:
+ if path in ('', '/'):
+ raise DeleteRootError(path)
+ super(SubFS,self).removedir(path,force=force)
+ if recursive:
+ try:
+ if dirname(path) not in ('', '/'):
self.removedir(dirname(path),recursive=True)
- except DirectoryNotEmptyError:
- pass
+ except DirectoryNotEmptyError:
+ pass
+
+# if path in ("","/"):
+# if not force:
+# for path2 in self.listdir(path):
+# raise DirectoryNotEmptyError(path)
+# else:
+# for path2 in self.listdir(path,absolute=True,files_only=True):
+# try:
+# self.remove(path2)
+# except ResourceNotFoundError:
+# pass
+# for path2 in self.listdir(path,absolute=True,dirs_only=True):
+# try:
+# self.removedir(path2,force=True)
+# except ResourceNotFoundError:
+# pass
+# else:
+# super(SubFS,self).removedir(path,force=force)
+# if recursive:
+# try:
+# if dirname(path):
+# self.removedir(dirname(path),recursive=True)
+# except DirectoryNotEmptyError:
+# pass