about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--TODO6
-rw-r--r--ranger/actions.py24
-rw-r--r--ranger/commands.py2
-rw-r--r--ranger/defaults/keys.py1
-rw-r--r--ranger/ext/shutil_generatorized.py66
-rw-r--r--ranger/fsobject/loader.py2
6 files changed, 74 insertions, 27 deletions
diff --git a/TODO b/TODO
index 9a5a4675..fe08e314 100644
--- a/TODO
+++ b/TODO
@@ -38,7 +38,8 @@ General
    (X) #48  10/01/19  abbreviate commands with first unambiguous substring
    ( ) #50  10/01/19  add more unit tests
    ( ) #51  10/01/21  remove directory.marked_items ?
-   ( ) #55  10/01/24  allow change of filename when pasting
+   (X) #55  10/01/24  allow change of filename when pasting
+          you're given the choice between overwriting or appending a "_"
    ( ) #56  10/01/30  warn before deleting mount points
    ( ) #57  10/01/30  warn before deleting unseen marked files
    (X) #58  10/02/04  change the title of the terminal
@@ -77,6 +78,7 @@ Ideas
 Goals for next minor version
 
    (X) #54  10/01/23  max_dirsize_for_autopreview not working
-   ( ) #55  10/01/24  allow change of filename when pasting
+   (X) #55  10/01/24  allow change of filename when pasting
+          you're given the choice between overwriting or appending a "_"
    (X) #61  10/02/09  show sum of size of marked files
 
diff --git a/ranger/actions.py b/ranger/actions.py
index bf6b5894..085bc36e 100644
--- a/ranger/actions.py
+++ b/ranger/actions.py
@@ -423,7 +423,7 @@ class Actions(EnvironmentAware, SettingsAware):
 			except Exception as x:
 				self.notify(x)
 
-	def paste(self):
+	def paste(self, overwrite=False):
 		"""Paste the selected items into the current directory"""
 		from os.path import join, isdir
 		from ranger.ext import shutil_generatorized as shutil_g
@@ -448,7 +448,9 @@ class Actions(EnvironmentAware, SettingsAware):
 				descr = "moving files from: " + one_file.dirname
 			def generate():
 				for f in copied_files:
-					for _ in shutil_g.move(f.path, original_path):
+					for _ in shutil_g.move(src=f.path,
+							dst=original_path,
+							overwrite=overwrite):
 						yield
 				pwd = self.env.get_directory(original_path)
 				pwd.load_content()
@@ -460,11 +462,15 @@ class Actions(EnvironmentAware, SettingsAware):
 			def generate():
 				for f in self.env.copy:
 					if isdir(f.path):
-						for _ in shutil_g.copytree(f.path,
-								join(self.env.pwd.path, f.basename)):
+						for _ in shutil_g.copytree(src=f.path,
+								dst=join(self.env.pwd.path, f.basename),
+								symlinks=True,
+								overwrite=overwrite):
 							yield
 					else:
-						for _ in shutil_g.copy2(f.path, original_path):
+						for _ in shutil_g.copy2(f.path, original_path,
+								symlinks=True,
+								overwrite=overwrite):
 							yield
 				pwd = self.env.get_directory(original_path)
 				pwd.load_content()
@@ -481,18 +487,18 @@ class Actions(EnvironmentAware, SettingsAware):
 					try:
 						shutil.rmtree(f.path)
 					except OSError as err:
-						self.notify(str(err), bad=True)
+						self.notify(err)
 				else:
 					try:
 						os.remove(f.path)
 					except OSError as err:
-						self.notify(str(err), bad=True)
+						self.notify(err)
 
 	def mkdir(self, name):
 		try:
 			os.mkdir(os.path.join(self.env.pwd.path, name))
 		except OSError as err:
-			self.notify(str(err), bad=True)
+			self.notify(err)
 
 
 	def rename(self, src, dest):
@@ -502,4 +508,4 @@ class Actions(EnvironmentAware, SettingsAware):
 		try:
 			os.rename(src, dest)
 		except OSError as err:
-			self.notify(str(err), bad=True)
+			self.notify(err)
diff --git a/ranger/commands.py b/ranger/commands.py
index 0903e00a..4f291b0a 100644
--- a/ranger/commands.py
+++ b/ranger/commands.py
@@ -364,7 +364,7 @@ class chmod(Command):
 			try:
 				os.chmod(file.path, mode)
 			except Exception as ex:
-				self.fm.notify(str(ex), bad=True)
+				self.fm.notify(ex)
 
 		try:
 			# reloading directory.  maybe its better to reload the selected
diff --git a/ranger/defaults/keys.py b/ranger/defaults/keys.py
index c800c795..6ffcca52 100644
--- a/ranger/defaults/keys.py
+++ b/ranger/defaults/keys.py
@@ -76,6 +76,7 @@ def initialize_commands(command_list):
 	bind('yy', fm.copy())
 	bind('dd', fm.cut())
 	bind('pp', fm.paste())
+	bind('po', fm.paste(overwrite=True))
 	bind('pl', fm.paste_symlink())
 	bind('p', hint='press //p// once again to confirm pasting' \
 			', or //l// to create symlinks')
diff --git a/ranger/ext/shutil_generatorized.py b/ranger/ext/shutil_generatorized.py
index 6c33dc58..8c699f8e 100644
--- a/ranger/ext/shutil_generatorized.py
+++ b/ranger/ext/shutil_generatorized.py
@@ -15,6 +15,8 @@ import fnmatch
 __all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2",
            "copytree","move","rmtree","Error", "SpecialFileError"]
 
+APPENDIX = '_'
+
 class Error(EnvironmentError):
     pass
 
@@ -95,7 +97,7 @@ def copystat(src, dst):
         os.chflags(dst, st.st_flags)
 
 
-def copy(src, dst):
+def copy(src, dst, overwrite=False):
     """Copy data and mode bits ("cp src dst").
 
     The destination may be a directory.
@@ -103,11 +105,13 @@ def copy(src, dst):
     """
     if os.path.isdir(dst):
         dst = os.path.join(dst, os.path.basename(src))
+    if not overwrite:
+        dst = get_safe_path(dst)
     for _ in copyfile(src, dst):
         yield
     copymode(src, dst)
 
-def copy2(src, dst):
+def copy2(src, dst, overwrite=False, symlinks=False):
     """Copy data and all stat info ("cp -p src dst").
 
     The destination may be a directory.
@@ -115,9 +119,30 @@ def copy2(src, dst):
     """
     if os.path.isdir(dst):
         dst = os.path.join(dst, os.path.basename(src))
-    for _ in copyfile(src, dst):
-        yield
-    copystat(src, dst)
+    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 ignore_patterns(*patterns):
     """Function that can be used as copytree() ignore parameter.
@@ -131,7 +156,7 @@ def ignore_patterns(*patterns):
         return set(ignored_names)
     return _ignore_patterns
 
-def copytree(src, dst, symlinks=False, ignore=None):
+def copytree(src, dst, symlinks=False, ignore=None, overwrite=False):
     """Recursively copy a directory tree using copy2().
 
     The destination directory must not already exist.
@@ -163,8 +188,13 @@ def copytree(src, dst, symlinks=False, ignore=None):
     else:
         ignored_names = set()
 
-    os.makedirs(dst)
     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
@@ -173,13 +203,19 @@ def copytree(src, dst, symlinks=False, ignore=None):
         try:
             if symlinks and os.path.islink(srcname):
                 linkto = os.readlink(srcname)
-                os.symlink(linkto, dstname)
+                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):
+                for _ in copytree(srcname, dstname, symlinks,
+                        ignore, overwrite):
                     yield
             else:
                 # Will raise a SpecialFileError for unsupported file types
-                for _ in copy2(srcname, dstname):
+                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
@@ -252,7 +288,7 @@ def _basename(path):
     # 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):
+def move(src, dst, overwrite=False):
     """Recursively move a file or directory to another location. This is
     similar to the Unix "mv" command.
 
@@ -270,21 +306,23 @@ def move(src, dst):
 
     """
     real_dst = dst
-    if os.path.isdir(dst):
+    if not overwrite and os.path.isdir(dst):
         real_dst = os.path.join(dst, _basename(src))
         if os.path.exists(real_dst):
             raise Error("Destination path '%s' already exists" % real_dst)
+    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):
+            for _ in copytree(src, real_dst, symlinks=True, overwrite=overwrite):
                 yield
             rmtree(src)
         else:
-            for _ in copy2(src, real_dst):
+            for _ in copy2(src, real_dst, symlinks=True, overwrite=overwrite):
                 yield
             os.unlink(src)
 
diff --git a/ranger/fsobject/loader.py b/ranger/fsobject/loader.py
index fcd416c0..556ff24b 100644
--- a/ranger/fsobject/loader.py
+++ b/ranger/fsobject/loader.py
@@ -119,7 +119,7 @@ class Loader(FileManagerAware):
 			item.load_generator = None
 			self.queue.remove(item)
 		except Exception as err:
-			self.fm.notify(str(err), bad=True)
+			self.fm.notify(err)
 
 	def has_work(self):
 		"""Is there anything to load?"""