From a986e2bda5ad96d1e9cf53dddabb71405e45e8c1 Mon Sep 17 00:00:00 2001 From: hut Date: Sun, 14 Feb 2010 21:59:46 +0100 Subject: actions: improved copying/moving --- TODO | 6 ++-- ranger/actions.py | 24 ++++++++------ ranger/commands.py | 2 +- ranger/defaults/keys.py | 1 + ranger/ext/shutil_generatorized.py | 66 ++++++++++++++++++++++++++++++-------- ranger/fsobject/loader.py | 2 +- 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?""" -- cgit 1.4.1-2-gfad0