diff options
-rw-r--r-- | ChangeLog | 1 | ||||
-rw-r--r-- | fs/__init__.py | 3 | ||||
-rw-r--r-- | fs/base.py | 307 | ||||
-rw-r--r-- | fs/errors.py | 5 | ||||
-rw-r--r-- | fs/ftpfs.py | 7 | ||||
-rw-r--r-- | fs/memoryfs.py | 10 | ||||
-rw-r--r-- | fs/mountfs.py | 2 | ||||
-rw-r--r-- | fs/multifs.py | 4 | ||||
-rw-r--r-- | fs/osfs/__init__.py | 9 | ||||
-rw-r--r-- | fs/osfs/watch.py | 2 | ||||
-rw-r--r-- | fs/osfs/xattrs.py | 8 | ||||
-rw-r--r-- | fs/s3fs.py | 2 | ||||
-rw-r--r-- | fs/sftpfs.py | 4 | ||||
-rw-r--r-- | fs/tests/__init__.py | 4 | ||||
-rw-r--r-- | fs/tests/test_expose.py | 1 | ||||
-rw-r--r-- | fs/tests/test_utils.py | 106 | ||||
-rw-r--r-- | fs/utils.py | 76 | ||||
-rw-r--r-- | fs/wrapfs/__init__.py | 7 | ||||
-rw-r--r-- | fs/wrapfs/lazyfs.py | 2 | ||||
-rw-r--r-- | fs/wrapfs/subfs.py | 53 |
20 files changed, 394 insertions, 219 deletions
@@ -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 @@ -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 @@ -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 |