about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authortoonn <toonn@toonn.io>2019-12-22 16:46:58 +0100
committertoonn <toonn@toonn.io>2019-12-22 16:46:58 +0100
commit3f8e7c14103a6570b0e55fbcf84242c86f42a7cb (patch)
treea902842f2f683ef5bd9930a537f31dd9f6fe4cad
parent3fc1f4c47c7bc64dd75d0b26e97eb981467466cc (diff)
parentf6c4d5f802b1b3a4c26393d28a11e5b098e802a0 (diff)
downloadranger-3f8e7c14103a6570b0e55fbcf84242c86f42a7cb.tar.gz
Merge branch 'laktak-paste_conflict'
-rw-r--r--doc/ranger.16
-rw-r--r--doc/ranger.pod4
-rwxr-xr-xranger/config/commands.py31
-rw-r--r--ranger/core/actions.py5
-rw-r--r--ranger/core/loader.py12
-rw-r--r--ranger/ext/safe_path.py22
-rw-r--r--ranger/ext/shutil_generatorized.py42
7 files changed, 89 insertions, 33 deletions
diff --git a/doc/ranger.1 b/doc/ranger.1
index b9c46d54..6b23bbfb 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RANGER 1"
-.TH RANGER 1 "ranger-1.9.2" "2019-12-21" "ranger manual"
+.TH RANGER 1 "ranger-1.9.2" "2019-12-22" "ranger manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
@@ -677,6 +677,10 @@ also \*(L"da\*(R", \*(L"dr\*(R" and \*(L"dt\*(R" shortcuts equivalent to \*(L"ya
 .IX Item "pp"
 Paste the files which were previously copied or cut, like pressing Ctrl+V in
 modern \s-1GUI\s0 programs.
+.Sp
+Conflicts will be renamed by appending an '_' (and a counter if necessary),
+resulting in \f(CW\*(C`file.ext_\*(C'\fR, \f(CW\*(C`file.ext_0\*(C'\fR, etc. If you prefer \f(CW\*(C`file_.ext\*(C'\fR you
+can use the \f(CW\*(C`paste_ext\*(C'\fR command.
 .IP "po" 14
 .IX Item "po"
 Paste the copied/cut files, overwriting existing files.
diff --git a/doc/ranger.pod b/doc/ranger.pod
index a955efd8..4f53ae35 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -638,6 +638,10 @@ 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 '_' (and a counter if necessary),
+resulting in C<file.ext_>, C<file.ext_0>, etc. If you prefer C<file_.ext> you
+can use the C<paste_ext> command.
+
 =item po
 
 Paste the copied/cut files, overwriting existing files.
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index cb6fa132..06d5abb7 100755
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -1959,3 +1959,34 @@ class yank(Command):
             in sorted(self.modes.keys())
             if mode
         )
+
+
+class paste_ext(Command):
+    """
+    :paste_ext
+
+    Like paste but tries to rename conflicting files so that the
+    file extension stays intact (e.g. file_.ext).
+    """
+
+    @staticmethod
+    def make_safe_path(dst):
+        if not os.path.exists(dst):
+            return dst
+
+        dst_name, dst_ext = os.path.splitext(dst)
+
+        if not dst_name.endswith("_"):
+            dst_name += "_"
+            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
+
+    def execute(self):
+        return self.fm.paste(make_safe_path=paste_ext.make_safe_path)
diff --git a/ranger/core/actions.py b/ranger/core/actions.py
index cd6c6afe..c3d7de86 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
 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
@@ -1587,7 +1588,7 @@ 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, overwrite=False, append=False, dest=None, make_safe_path=get_safe_path):
         """:paste
 
         Paste the selected items into the current directory or to dest
@@ -1597,7 +1598,7 @@ class Actions(  # pylint: disable=too-many-instance-attributes,too-many-public-m
             dest = self.thistab.path
         if isdir(dest):
             loadable = CopyLoader(self.copy_buffer, self.do_cut, overwrite,
-                                  dest)
+                                  dest, make_safe_path)
             self.loader.add(loadable, append=append)
             self.do_cut = False
         else:
diff --git a/ranger/core/loader.py b/ranger/core/loader.py
index 26b729b6..7d4b21e8 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -19,6 +19,7 @@ except ImportError:
     HAVE_CHARDET = False
 
 from ranger.core.shared import FileManagerAware
+from ranger.ext.safe_path import get_safe_path
 from ranger.ext.signals import SignalDispatcher
 from ranger.ext.human_readable import human_readable
 
@@ -51,12 +52,14 @@ 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, overwrite=False, dest=None,
+                 make_safe_path=get_safe_path):
         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.make_safe_path = make_safe_path
         self.percent = 0
         if self.copy_buffer:
             self.one_file = self.copy_buffer[0]
@@ -108,7 +111,8 @@ 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):
+                                       overwrite=self.overwrite,
+                                       make_safe_path=self.make_safe_path):
                     self.percent = ((done + n) / size) * 100.
                     yield
                 done += n
@@ -125,6 +129,7 @@ class CopyLoader(Loadable, FileManagerAware):  # pylint: disable=too-many-instan
                             dst=os.path.join(self.original_path, fobj.basename),
                             symlinks=True,
                             overwrite=self.overwrite,
+                            make_safe_path=self.make_safe_path,
                     ):
                         self.percent = ((done + n) / size) * 100.
                         yield
@@ -132,7 +137,8 @@ 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, overwrite=self.overwrite,
+                                            make_safe_path=self.make_safe_path):
                         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..b172b577
--- /dev/null
+++ b/ranger/ext/safe_path.py
@@ -0,0 +1,22 @@
+# This file is part of ranger, the console file manager.
+# License: GNU GPL version 3, see the file "AUTHORS" for details.
+
+import os
+
+SUFFIX = '_'
+
+
+def get_safe_path(dst):
+    if not os.path.exists(dst):
+        return dst
+    if not dst.endswith(SUFFIX):
+        dst += SUFFIX
+        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
diff --git a/ranger/ext/shutil_generatorized.py b/ranger/ext/shutil_generatorized.py
index fe3cf068..a97a4d2d 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, overwrite=False, symlinks=False, make_safe_path=get_safe_path):
     """Copy data and all stat info ("cp -p src dst").
 
     The destination may be a directory.
@@ -162,7 +146,7 @@ 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)
+        dst = make_safe_path(dst)
     if symlinks and os.path.islink(src):
         linkto = os.readlink(src)
         if overwrite and os.path.lexists(dst):
@@ -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, overwrite=False, make_safe_path=get_safe_path):
     """Recursively copy a directory tree using copy2().
 
     The destination directory must not already exist.
@@ -211,7 +195,7 @@ def copytree(src, dst,  # pylint: disable=too-many-locals,too-many-branches
         os.makedirs(dst)
     except OSError:
         if not overwrite:
-            dst = get_safe_path(dst)
+            dst = make_safe_path(dst)
             os.makedirs(dst)
     errors = []
     done = 0
@@ -229,13 +213,15 @@ def copytree(src, dst,  # pylint: disable=too-many-locals,too-many-branches
                 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, overwrite,
+                                  make_safe_path):
                     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, overwrite=overwrite, symlinks=symlinks,
+                               make_safe_path=make_safe_path):
                     yield done + n
                 done += n
         # catch the Error from the recursive copytree so that we can
@@ -256,7 +242,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, overwrite=False, make_safe_path=get_safe_path):
     """Recursively move a file or directory to another location. This is
     similar to the Unix "mv" command.
 
@@ -283,17 +269,19 @@ def move(src, dst, overwrite=False):
 
         real_dst = os.path.join(dst, _basename(src))
     if not overwrite:
-        real_dst = get_safe_path(real_dst)
+        real_dst = make_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 done in copytree(src, real_dst, symlinks=True, overwrite=overwrite):
+            for done in copytree(src, real_dst, symlinks=True, overwrite=overwrite,
+                                 make_safe_path=make_safe_path):
                 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, overwrite=overwrite,
+                              make_safe_path=make_safe_path):
                 yield done
             os.unlink(src)