summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--ranger/core/actions.py70
-rw-r--r--ranger/ext/run_forked.py26
-rw-r--r--ranger/ext/shutil_generatorized.py302
3 files changed, 364 insertions, 34 deletions
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 2f76527a..9d839d4e 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -22,7 +22,8 @@ from ranger.core.shared import FileManagerAware, EnvironmentAware, \
 		SettingsAware
 from ranger.core.tab import Tab
 from ranger.fsobject import File
-from ranger.core.loader import CommandLoader
+from ranger.core.loader import CommandLoader, Loadable
+from ranger.ext import shutil_generatorized as shutil_g
 
 MACRO_FAIL = "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>"
 
@@ -1061,53 +1062,54 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 
 	def paste(self, overwrite=False):
 		"""Paste the selected items into the current directory"""
-		copied_files = tuple(self.copy_buffer)
+		copied_files = tuple(self.env.copy)
 
 		if not copied_files:
 			return
 
-		def refresh(_):
-			cwd = self.get_directory(original_path)
-			cwd.load_content()
-
-		cwd = self.thisdir
-		original_path = cwd.path
-		one_file = copied_files[0]
-		if overwrite:
-			cp_flags = ['-af', '--']
-			mv_flags = ['-f', '--']
-		else:
-			cp_flags = ['--backup=numbered', '-a', '--']
-			mv_flags = ['--backup=numbered', '--']
+		original_path = self.env.cwd.path
+		try:
+			one_file = copied_files[0]
+		except:
+			one_file = None
 
-		if self.do_cut:
-			self.copy_buffer.clear()
-			self.do_cut = False
+		if self.env.cut:
+			self.env.copy.clear()
+			self.env.cut = False
 			if len(copied_files) == 1:
 				descr = "moving: " + one_file.path
 			else:
 				descr = "moving files from: " + one_file.dirname
-			obj = CommandLoader(args=['mv'] + mv_flags \
-					+ [f.path for f in copied_files] \
-					+ [cwd.path], descr=descr)
+			def generate():
+				for f in copied_files:
+					for _ in shutil_g.move(src=f.path,
+							dst=original_path,
+							overwrite=overwrite):
+						yield
+				cwd = self.env.get_directory(original_path)
+				cwd.load_content()
 		else:
 			if len(copied_files) == 1:
 				descr = "copying: " + one_file.path
 			else:
 				descr = "copying files from: " + one_file.dirname
-			if not overwrite and len(copied_files) == 1 \
-					and one_file.dirname == cwd.path:
-				# Special case: yypp
-				# copying a file onto itself -> create a backup
-				obj = CommandLoader(args=['cp', '-f'] + cp_flags \
-						+ [one_file.path, one_file.path], descr=descr)
-			else:
-				obj = CommandLoader(args=['cp'] + cp_flags \
-						+ [f.path for f in copied_files] \
-						+ [cwd.path], descr=descr)
-
-		obj.signal_bind('after', refresh)
-		self.loader.add(obj)
+			def generate():
+				for f in self.env.copy:
+					if isdir(f.path):
+						for _ in shutil_g.copytree(src=f.path,
+								dst=join(original_path, f.basename),
+								symlinks=True,
+								overwrite=overwrite):
+							yield
+					else:
+						for _ in shutil_g.copy2(f.path, original_path,
+								symlinks=True,
+								overwrite=overwrite):
+							yield
+				cwd = self.env.get_directory(original_path)
+				cwd.load_content()
+
+		self.loader.add(Loadable(generate(), descr))
 
 	def delete(self):
 		# XXX: warn when deleting mount points/unseen marked files?
diff --git a/ranger/ext/run_forked.py b/ranger/ext/run_forked.py
new file mode 100644
index 00000000..0dd52252
--- /dev/null
+++ b/ranger/ext/run_forked.py
@@ -0,0 +1,26 @@
+# Copyright (C) 2012  Roman Zimbelmann <romanz@lavabit.com>
+# This software is distributed under the terms of the GNU GPL version 3.
+
+import os
+import subprocess
+
+def Popen_forked(*args, **kwargs):
+	"""
+	Forks process and runs Popen with the given args and kwargs.
+
+	If os.fork() is not supported, runs Popen without forking and returns the
+	process object returned by Popen.
+	Otherwise, returns None.
+	"""
+	try:
+		pid = os.fork()
+	except:
+		# fall back to not forking if os.fork() is not supported
+		return subprocess.Popen(*args, **kwargs)
+	else:
+		if pid == 0:
+			os.setsid()
+			kwargs['stdin'] = open(os.devnull, 'r')
+			kwargs['stdout'] = kwargs['stderr'] = open(os.devnull, 'w')
+			subprocess.Popen(*args, **kwargs)
+			os._exit(0)
diff --git a/ranger/ext/shutil_generatorized.py b/ranger/ext/shutil_generatorized.py
new file mode 100644
index 00000000..436d2cb7
--- /dev/null
+++ b/ranger/ext/shutil_generatorized.py
@@ -0,0 +1,302 @@
+# This file was taken from the python standard library and has been
+# slightly modified to do a "yield" after every 16KB of copying
+"""Utility functions for copying files and directory trees.
+
+XXX The functions here don't copy the resource fork or other metadata on Mac.
+
+"""
+
+import os
+import sys
+import stat
+from os.path import abspath
+
+__all__ = ["copyfileobj","copyfile","copystat","copy2",
+           "copytree","move","rmtree","Error", "SpecialFileError"]
+
+APPENDIX = '_'
+
+class Error(EnvironmentError):
+    pass
+
+class SpecialFileError(EnvironmentError):
+    """Raised when trying to do a kind of operation (e.g. copying) which is
+    not supported on a special file (e.g. a named pipe)"""
+
+try:
+    WindowsError
+except NameError:
+    WindowsError = None
+
+def copyfileobj(fsrc, fdst, length=16*1024):
+    """copy data from file-like object fsrc to file-like object fdst"""
+    while 1:
+        buf = fsrc.read(length)
+        if not buf:
+            break
+        fdst.write(buf)
+        yield
+
+def _samefile(src, dst):
+    # Macintosh, Unix.
+    if hasattr(os.path,'samefile'):
+        try:
+            return os.path.samefile(src, dst)
+        except OSError:
+            return False
+
+    # All other platforms: check for same pathname.
+    return (os.path.normcase(abspath(src)) ==
+            os.path.normcase(abspath(dst)))
+
+def copyfile(src, dst):
+    """Copy data from src to dst"""
+    if _samefile(src, dst):
+        raise Error("`%s` and `%s` are the same file" % (src, dst))
+
+    fsrc = None
+    fdst = None
+    for fn in [src, dst]:
+        try:
+            st = os.stat(fn)
+        except OSError:
+            # File most likely does not exist
+            pass
+        else:
+            # XXX What about other special files? (sockets, devices...)
+            if stat.S_ISFIFO(st.st_mode):
+                raise SpecialFileError("`%s` is a named pipe" % fn)
+    try:
+        fsrc = open(src, 'rb')
+        fdst = open(dst, 'wb')
+        for _ in copyfileobj(fsrc, fdst):
+            yield
+    finally:
+        if fdst:
+            fdst.close()
+        if fsrc:
+            fsrc.close()
+
+def copystat(src, dst):
+    """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
+    st = os.stat(src)
+    mode = stat.S_IMODE(st.st_mode)
+    if hasattr(os, 'utime'):
+        try: os.utime(dst, (st.st_atime, st.st_mtime))
+        except: pass
+    if hasattr(os, 'chmod'):
+        try: os.chmod(dst, mode)
+        except: pass
+    if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
+        try: os.chflags(dst, st.st_flags)
+        except: pass
+
+def copy2(src, dst, overwrite=False, symlinks=False):
+    """Copy data and all stat info ("cp -p src dst").
+
+    The destination may be a directory.
+
+    """
+    if os.path.isdir(dst):
+        dst = os.path.join(dst, os.path.basename(src))
+    if not overwrite:
+        dst = get_safe_path(dst)
+    if symlinks and os.path.islink(src):
+        linkto = os.readlink(src)
+        os.symlink(linkto, dst)
+    else:
+        for _ in copyfile(src, dst):
+            yield
+        copystat(src, dst)
+
+def get_safe_path(dst):
+    if not os.path.exists(dst):
+        return dst
+    if not dst.endswith(APPENDIX):
+        dst += APPENDIX
+        if not os.path.exists(dst):
+            return dst
+    n = 0
+    test_dst = dst + str(n)
+    while os.path.exists(test_dst):
+        n += 1
+        test_dst = dst + str(n)
+
+    return test_dst
+
+def copytree(src, dst, symlinks=False, ignore=None, overwrite=False):
+    """Recursively copy a directory tree using copy2().
+
+    The destination directory must not already exist.
+    If exception(s) occur, an Error is raised with a list of reasons.
+
+    If the optional symlinks flag is true, symbolic links in the
+    source tree result in symbolic links in the destination tree; if
+    it is false, the contents of the files pointed to by symbolic
+    links are copied.
+
+    The optional ignore argument is a callable. If given, it
+    is called with the `src` parameter, which is the directory
+    being visited by copytree(), and `names` which is the list of
+    `src` contents, as returned by os.listdir():
+
+        callable(src, names) -> ignored_names
+
+    Since copytree() is called recursively, the callable will be
+    called once for each directory that is copied. It returns a
+    list of names relative to the `src` directory that should
+    not be copied.
+
+    XXX Consider this example code rather than the ultimate tool.
+
+    """
+    names = os.listdir(src)
+    if ignore is not None:
+        ignored_names = ignore(src, names)
+    else:
+        ignored_names = set()
+
+    errors = []
+    try:
+        os.makedirs(dst)
+    except Exception as err:
+        if not overwrite:
+            dst = get_safe_path(dst)
+            os.makedirs(dst)
+    for name in names:
+        if name in ignored_names:
+            continue
+        srcname = os.path.join(src, name)
+        dstname = os.path.join(dst, name)
+        try:
+            if symlinks and os.path.islink(srcname):
+                linkto = os.readlink(srcname)
+                if os.path.lexists(dstname):
+                    if not os.path.islink(dstname) \
+                    or os.readlink(dstname) != linkto:
+                        os.unlink(dstname)
+                        os.symlink(linkto, dstname)
+            elif os.path.isdir(srcname):
+                for _ in copytree(srcname, dstname, symlinks,
+                        ignore, overwrite):
+                    yield
+            else:
+                # Will raise a SpecialFileError for unsupported file types
+                for _ in copy2(srcname, dstname,
+                        overwrite=overwrite, symlinks=symlinks):
+                    yield
+        # catch the Error from the recursive copytree so that we can
+        # continue with other files
+        except Error as err:
+            errors.extend(err.args[0])
+        except EnvironmentError as why:
+            errors.append((srcname, dstname, str(why)))
+    try:
+        copystat(src, dst)
+    except OSError as why:
+        if WindowsError is not None and isinstance(why, WindowsError):
+            # Copying file access times may fail on Windows
+            pass
+        else:
+            errors.extend((src, dst, str(why)))
+    if errors:
+        raise Error(errors)
+
+def rmtree(path, ignore_errors=False, onerror=None):
+    """Recursively delete a directory tree.
+
+    If ignore_errors is set, errors are ignored; otherwise, if onerror
+    is set, it is called to handle the error with arguments (func,
+    path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
+    path is the argument to that function that caused it to fail; and
+    exc_info is a tuple returned by sys.exc_info().  If ignore_errors
+    is false and onerror is None, an exception is raised.
+
+    """
+    if ignore_errors:
+        def onerror(*args):
+            pass
+    elif onerror is None:
+        def onerror(*args):
+            raise
+    try:
+        if os.path.islink(path):
+            # symlinks to directories are forbidden, see bug #1669
+            raise OSError("Cannot call rmtree on a symbolic link")
+    except OSError:
+        onerror(os.path.islink, path, sys.exc_info())
+        # can't continue even if onerror hook returns
+        return
+    names = []
+    try:
+        names = os.listdir(path)
+    except os.error as err:
+        onerror(os.listdir, path, sys.exc_info())
+    for name in names:
+        fullname = os.path.join(path, name)
+        try:
+            mode = os.lstat(fullname).st_mode
+        except os.error:
+            mode = 0
+        if stat.S_ISDIR(mode):
+            rmtree(fullname, ignore_errors, onerror)
+        else:
+            try:
+                os.remove(fullname)
+            except os.error as err:
+                onerror(os.remove, fullname, sys.exc_info())
+    try:
+        os.rmdir(path)
+    except os.error:
+        onerror(os.rmdir, path, sys.exc_info())
+
+
+def _basename(path):
+    # A basename() variant which first strips the trailing slash, if present.
+    # Thus we always get the last component of the path, even for directories.
+    return os.path.basename(path.rstrip(os.path.sep))
+
+def move(src, dst, overwrite=False):
+    """Recursively move a file or directory to another location. This is
+    similar to the Unix "mv" command.
+
+    If the destination is a directory or a symlink to a directory, the source
+    is moved inside the directory. The destination path must not already
+    exist.
+
+    If the destination already exists but is not a directory, it may be
+    overwritten depending on os.rename() semantics.
+
+    If the destination is on our current filesystem, then rename() is used.
+    Otherwise, src is copied to the destination and then removed.
+    A lot more could be done here...  A look at a mv.c shows a lot of
+    the issues this implementation glosses over.
+
+    """
+    real_dst = os.path.join(dst, _basename(src))
+    if not overwrite:
+        real_dst = get_safe_path(real_dst)
+    try:
+        os.rename(src, real_dst)
+    except OSError:
+        if os.path.isdir(src):
+            if _destinsrc(src, dst):
+                raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
+            for _ in copytree(src, real_dst, symlinks=True, overwrite=overwrite):
+                yield
+            rmtree(src)
+        else:
+            for _ in copy2(src, real_dst, symlinks=True, overwrite=overwrite):
+                yield
+            os.unlink(src)
+
+def _destinsrc(src, dst):
+    src = abspath(src)
+    dst = abspath(dst)
+    if not src.endswith(os.path.sep):
+        src += os.path.sep
+    if not dst.endswith(os.path.sep):
+        dst += os.path.sep
+    return dst.startswith(src)
+
+# vi: expandtab sts=4 ts=4 sw=4