summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--ranger/core/actions.py40
-rw-r--r--ranger/core/loader.py30
-rw-r--r--ranger/ext/shutil_generatorized.py302
3 files changed, 41 insertions, 331 deletions
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index e1346612..86d37915 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -26,8 +26,7 @@ from ranger import fsobject
 from ranger.shared import FileManagerAware, EnvironmentAware, SettingsAware
 from ranger.gui.widgets import console_mode as cmode
 from ranger.fsobject import File
-from ranger.ext import shutil_generatorized as shutil_g
-from ranger.core.loader import LoadableObject
+from ranger.core.loader import CommandLoader
 
 class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 	search_method = 'ctime'
@@ -569,6 +568,10 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 		if not copied_files:
 			return
 
+		def refresh(_):
+			cwd = self.env.get_directory(original_path)
+			cwd.load_content()
+
 		original_path = self.env.cwd.path
 		try:
 			one_file = copied_files[0]
@@ -582,36 +585,19 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware):
 				descr = "moving: " + one_file.path
 			else:
 				descr = "moving files from: " + one_file.dirname
-			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()
+			obj = CommandLoader(args=['mv', '--backup=existing',
+					'-t', self.env.cwd.path] + [f.path for f in copied_files],
+					descr=descr, end_hook=refresh)
 		else:
 			if len(copied_files) == 1:
 				descr = "copying: " + one_file.path
 			else:
 				descr = "copying files from: " + one_file.dirname
-			def generate():
-				for f in self.env.copy:
-					if isdir(f.path):
-						for _ in shutil_g.copytree(src=f.path,
-								dst=join(self.env.cwd.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(LoadableObject(generate(), descr))
+			obj = CommandLoader(args=['cp', '--backup=existing', '--archive',
+					'-t', self.env.cwd.path] + [f.path for f in self.env.copy],
+					descr=descr, end_hook=refresh)
+
+		self.loader.add(obj)
 
 	def delete(self):
 		self.notify("Deleting!", duration=1)
diff --git a/ranger/core/loader.py b/ranger/core/loader.py
index 4f4424e4..24dac464 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -14,9 +14,13 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from collections import deque
-from time import time
-from ranger.shared import FileManagerAware
+from time import time, sleep
+from subprocess import Popen, PIPE
 import math
+import os
+import select
+
+from ranger.shared import FileManagerAware
 
 def status_generator():
 	"""Generate a rotating line which can be used as a throbber"""
@@ -35,6 +39,28 @@ class LoadableObject(object):
 		return self.description
 
 
+class CommandLoader(LoadableObject):
+	def __init__(self, args, descr, begin_hook=None, end_hook=None):
+		LoadableObject.__init__(self, self.generate(), descr)
+		self.args = args
+		self.begin_hook = begin_hook
+		self.end_hook = end_hook
+
+	def generate(self):
+		process = Popen(self.args, stdout=open(os.devnull, 'w'), stderr=PIPE)
+		if self.begin_hook:
+			self.begin_hook(process)
+		while process.poll() is None:
+			rd, _, __ = select.select(
+					[process.stderr], [], [], 0.05)
+			if rd:
+				self.notify(process.stderr.readline(), bad=True)
+			sleep(0.02)
+			yield
+		if self.end_hook(process):
+			self.end_hook(process)
+
+
 class Loader(FileManagerAware):
 	seconds_of_work_time = 0.03
 
diff --git a/ranger/ext/shutil_generatorized.py b/ranger/ext/shutil_generatorized.py
deleted file mode 100644
index 436d2cb7..00000000
--- a/ranger/ext/shutil_generatorized.py
+++ /dev/null
@@ -1,302 +0,0 @@
-# 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