summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorChristian Zangl <laktak@cdak.net>2019-07-14 14:47:13 +0200
committerChristian Zangl <laktak@cdak.net>2019-07-14 15:05:43 +0200
commit9d817dc755285f10e556e7a100fa22f8480397ce (patch)
tree3a6aa62ced5c2e9d0f14076ae915becd65ccdec2
parent669100aad83e3a51f140b6085d836a8260eda8d0 (diff)
downloadranger-9d817dc755285f10e556e7a100fa22f8480397ce.tar.gz
Added a conflict parameter to the paste action
It allows you to specify 'overwrite', 'rename', or 'rename_ext' (=default) in case of a file name conflict. This obsoletes the overwrite parameter.
-rw-r--r--doc/ranger.pod3
-rw-r--r--examples/rc_emacs.conf4
-rw-r--r--ranger/config/rc.conf6
-rw-r--r--ranger/core/actions.py20
-rw-r--r--ranger/core/loader.py10
-rw-r--r--ranger/ext/safe_path.py41
-rw-r--r--ranger/ext/shutil_generatorized.py49
7 files changed, 89 insertions, 44 deletions
diff --git a/doc/ranger.pod b/doc/ranger.pod
index fadd4cba..17a9fdc8 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -527,6 +527,9 @@ also "da", "dr" and "dt" shortcuts equivalent to "ya", "yr" and "yt".)
 Paste the files which were previously copied or cut, like pressing Ctrl+V in
 modern GUI programs.
 
+Conflicts will be renamed by appending an _ to the file extension. To change
+this see the paste command.
+
 =item po
 
 Paste the copied/cut files, overwriting existing files.
diff --git a/examples/rc_emacs.conf b/examples/rc_emacs.conf
index 0462282e..583fce5c 100644
--- a/examples/rc_emacs.conf
+++ b/examples/rc_emacs.conf
@@ -335,8 +335,8 @@ map <A-d>  console rename%space
 map <C-e>  eval fm.open_console('rename ' + fm.thisfile.relative_path)
 map <C-a>  eval fm.open_console('rename ' + fm.thisfile.relative_path, position=7)
 
-map <C-y>y  paste
-map <C-y>o  paste overwrite=True
+map <C-y>y  paste conflict=rename_ext
+map <C-y>o  paste conflict=overwrite
 map <C-y>l  paste_symlink relative=False
 map <C-y>L  paste_symlink relative=True
 map <C-y>hl paste_hardlink
diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf
index f559290d..2efcccae 100644
--- a/ranger/config/rc.conf
+++ b/ranger/config/rc.conf
@@ -470,10 +470,10 @@ map a  rename_append
 map A  eval fm.open_console('rename ' + fm.thisfile.relative_path.replace("%", "%%"))
 map I  eval fm.open_console('rename ' + fm.thisfile.relative_path.replace("%", "%%"), position=7)
 
-map pp paste
-map po paste overwrite=True
+map pp paste conflict=rename_ext
+map po paste conflict=overwrite
 map pP paste append=True
-map pO paste overwrite=True append=True
+map pO paste conflict=overwrite append=True
 map pl paste_symlink relative=False
 map pL paste_symlink relative=True
 map phl paste_hardlink
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index 435fcf13..8adc075a 100644
--- a/ranger/core/actions.py
+++ b/ranger/core/actions.py
@@ -24,6 +24,7 @@ import ranger
 from ranger.ext.direction import Direction
 from ranger.ext.relative_symlink import relative_symlink
 from ranger.ext.keybinding_parser import key_to_string, construct_keybinding
+from ranger.ext.safe_path import get_safe_path, get_safe_path_classic
 from ranger.ext.shell_escape import shell_quote
 from ranger.ext.next_available_filename import next_available_filename
 from ranger.ext.rifle import squash_flags, ASK_COMMAND
@@ -1591,14 +1592,29 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
                 link(source_path,
                      next_available_filename(target_path))
 
-    def paste(self, overwrite=False, append=False, dest=None):
+    def paste(self, conflict='rename_ext', append=False, dest=None, overwrite=False):
         """:paste
 
         Paste the selected items into the current directory or to dest
         if provided.
         """
+        # overwrite is obsolete, it was replaced by conflict
+        if overwrite:
+            conflict = 'overwrite'
+
+        if conflict == 'overwrite':
+            resolve_conflict = None
+        elif conflict == 'rename':
+            resolve_conflict = get_safe_path
+        elif conflict == 'rename_ext':
+            resolve_conflict = get_safe_path_classic
+        else:
+            self.notify('Failed to paste. The conflict parameter ' + conflict
+                        + ' is invalid.', bad=True)
+            return
+
         if dest is None or isdir(dest):
-            loadable = CopyLoader(self.copy_buffer, self.do_cut, overwrite,
+            loadable = CopyLoader(self.copy_buffer, self.do_cut, resolve_conflict,
                                   dest)
             self.loader.add(loadable, append=append)
             self.do_cut = False
diff --git a/ranger/core/loader.py b/ranger/core/loader.py
index 26b729b6..bf4c780b 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -51,12 +51,12 @@ class Loadable(object):
 class CopyLoader(Loadable, FileManagerAware):  # pylint: disable=too-many-instance-attributes
     progressbar_supported = True
 
-    def __init__(self, copy_buffer, do_cut=False, overwrite=False, dest=None):
+    def __init__(self, copy_buffer, do_cut=False, resolve_conflict=False, dest=None):
         self.copy_buffer = tuple(copy_buffer)
         self.do_cut = do_cut
         self.original_copy_buffer = copy_buffer
         self.original_path = dest if dest is not None else self.fm.thistab.path
-        self.overwrite = overwrite
+        self.resolve_conflict = resolve_conflict
         self.percent = 0
         if self.copy_buffer:
             self.one_file = self.copy_buffer[0]
@@ -108,7 +108,7 @@ class CopyLoader(Loadable, FileManagerAware):  # pylint: disable=too-many-instan
                         self.fm.tags.dump()
                 n = 0
                 for n in shutil_g.move(src=fobj.path, dst=self.original_path,
-                                       overwrite=self.overwrite):
+                                       resolve_conflict=self.resolve_conflict):
                     self.percent = ((done + n) / size) * 100.
                     yield
                 done += n
@@ -124,7 +124,7 @@ class CopyLoader(Loadable, FileManagerAware):  # pylint: disable=too-many-instan
                             src=fobj.path,
                             dst=os.path.join(self.original_path, fobj.basename),
                             symlinks=True,
-                            overwrite=self.overwrite,
+                            resolve_conflict=self.resolve_conflict,
                     ):
                         self.percent = ((done + n) / size) * 100.
                         yield
@@ -132,7 +132,7 @@ class CopyLoader(Loadable, FileManagerAware):  # pylint: disable=too-many-instan
                 else:
                     n = 0
                     for n in shutil_g.copy2(fobj.path, self.original_path,
-                                            symlinks=True, overwrite=self.overwrite):
+                                            symlinks=True, resolve_conflict=self.resolve_conflict):
                         self.percent = ((done + n) / size) * 100.
                         yield
                     done += n
diff --git a/ranger/ext/safe_path.py b/ranger/ext/safe_path.py
new file mode 100644
index 00000000..22ab42ee
--- /dev/null
+++ b/ranger/ext/safe_path.py
@@ -0,0 +1,41 @@
+# This file is part of ranger, the console file manager.
+# License: GNU GPL version 3, see the file "AUTHORS" for details.
+
+import os
+
+APPENDIX = '_'
+
+
+def get_safe_path_classic(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 get_safe_path(dst):
+    if not os.path.exists(dst):
+        return dst
+
+    dst_name, dst_ext = os.path.splitext(dst)
+
+    if not dst_name.endswith(APPENDIX):
+        dst_name += APPENDIX
+        if not os.path.exists(dst_name + dst_ext):
+            return dst_name + dst_ext
+    n = 0
+    test_dst = dst_name + str(n)
+    while os.path.exists(test_dst + dst_ext):
+        n += 1
+        test_dst = dst_name + str(n)
+
+    return test_dst + dst_ext
diff --git a/ranger/ext/shutil_generatorized.py b/ranger/ext/shutil_generatorized.py
index fe3cf068..6f736174 100644
--- a/ranger/ext/shutil_generatorized.py
+++ b/ranger/ext/shutil_generatorized.py
@@ -7,11 +7,11 @@ import os
 import stat
 import sys
 from shutil import (_samefile, rmtree, _basename, _destinsrc, Error, SpecialFileError)
+from ranger.ext.safe_path import get_safe_path
 
 __all__ = ["copyfileobj", "copyfile", "copystat", "copy2", "BLOCK_SIZE",
            "copytree", "move", "rmtree", "Error", "SpecialFileError"]
 
-APPENDIX = '_'
 BLOCK_SIZE = 16 * 1024
 
 
@@ -103,22 +103,6 @@ else:
             pass
 
 
-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 copyfileobj(fsrc, fdst, length=BLOCK_SIZE):
     """copy data from file-like object fsrc to file-like object fdst"""
     done = 0
@@ -153,7 +137,7 @@ def copyfile(src, dst):
                 yield done
 
 
-def copy2(src, dst, overwrite=False, symlinks=False):
+def copy2(src, dst, resolve_conflict=get_safe_path, symlinks=False):
     """Copy data and all stat info ("cp -p src dst").
 
     The destination may be a directory.
@@ -161,11 +145,11 @@ def copy2(src, dst, overwrite=False, symlinks=False):
     """
     if os.path.isdir(dst):
         dst = os.path.join(dst, os.path.basename(src))
-    if not overwrite:
-        dst = get_safe_path(dst)
+    if resolve_conflict:
+        dst = resolve_conflict(dst)
     if symlinks and os.path.islink(src):
         linkto = os.readlink(src)
-        if overwrite and os.path.lexists(dst):
+        if not resolve_conflict and os.path.lexists(dst):
             os.unlink(dst)
         os.symlink(linkto, dst)
     else:
@@ -175,7 +159,7 @@ def copy2(src, dst, overwrite=False, symlinks=False):
 
 
 def copytree(src, dst,  # pylint: disable=too-many-locals,too-many-branches
-             symlinks=False, ignore=None, overwrite=False):
+             symlinks=False, ignore=None, resolve_conflict=get_safe_path):
     """Recursively copy a directory tree using copy2().
 
     The destination directory must not already exist.
@@ -210,8 +194,8 @@ def copytree(src, dst,  # pylint: disable=too-many-locals,too-many-branches
     try:
         os.makedirs(dst)
     except OSError:
-        if not overwrite:
-            dst = get_safe_path(dst)
+        if resolve_conflict:
+            dst = resolve_conflict(dst)
             os.makedirs(dst)
     errors = []
     done = 0
@@ -223,19 +207,20 @@ def copytree(src, dst,  # pylint: disable=too-many-locals,too-many-branches
         try:
             if symlinks and os.path.islink(srcname):
                 linkto = os.readlink(srcname)
-                if overwrite and os.path.lexists(dstname):
+                if not resolve_conflict and os.path.lexists(dstname):
                     os.unlink(dstname)
                 os.symlink(linkto, dstname)
                 copystat(srcname, dstname)
             elif os.path.isdir(srcname):
                 n = 0
-                for n in copytree(srcname, dstname, symlinks, ignore, overwrite):
+                for n in copytree(srcname, dstname, symlinks, ignore, resolve_conflict):
                     yield done + n
                 done += n
             else:
                 # Will raise a SpecialFileError for unsupported file types
                 n = 0
-                for n in copy2(srcname, dstname, overwrite=overwrite, symlinks=symlinks):
+                for n in copy2(srcname, dstname, resolve_conflict=resolve_conflict,
+                               symlinks=symlinks):
                     yield done + n
                 done += n
         # catch the Error from the recursive copytree so that we can
@@ -256,7 +241,7 @@ def copytree(src, dst,  # pylint: disable=too-many-locals,too-many-branches
         raise Error(errors)
 
 
-def move(src, dst, overwrite=False):
+def move(src, dst, resolve_conflict=get_safe_path):
     """Recursively move a file or directory to another location. This is
     similar to the Unix "mv" command.
 
@@ -282,18 +267,18 @@ def move(src, dst, overwrite=False):
             return
 
         real_dst = os.path.join(dst, _basename(src))
-    if not overwrite:
-        real_dst = get_safe_path(real_dst)
+    if resolve_conflict:
+        real_dst = resolve_conflict(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 done in copytree(src, real_dst, symlinks=True, overwrite=overwrite):
+            for done in copytree(src, real_dst, symlinks=True, resolve_conflict=resolve_conflict):
                 yield done
             rmtree(src)
         else:
-            for done in copy2(src, real_dst, symlinks=True, overwrite=overwrite):
+            for done in copy2(src, real_dst, symlinks=True, resolve_conflict=resolve_conflict):
                 yield done
             os.unlink(src)