diff --git a/navigator/components/NavWindow.js b/navigator/components/NavWindow.js index 588a2a6..73ac0d2 100644 --- a/navigator/components/NavWindow.js +++ b/navigator/components/NavWindow.js @@ -645,6 +645,7 @@ export class NavWindow { cmd.push(item.path_str()); } cmd.push(dest); + console.log(cmd); this.clip_board.length = 0; // clear clipboard var promise = new Promise((resolve, reject) => { var proc = cockpit.spawn( @@ -700,13 +701,15 @@ export class NavWindow { proc.input(JSON.stringify(user_response) + "\n", true); } } else { - await this.modal_prompt.alert(payload["message"]); + await this.modal_prompt.alert(payload["message"], payload?.detail); } }); proc.done((data) => { resolve(); }); proc.fail((e, data) => { + console.log(e); + console.log(data); reject("Paste failed."); }); }); diff --git a/navigator/scripts/paste.py3 b/navigator/scripts/paste.py3 index 1e0a886..c5efc35 100755 --- a/navigator/scripts/paste.py3 +++ b/navigator/scripts/paste.py3 @@ -22,13 +22,41 @@ Synopsis: `paste.py3 [-m] " + self.dst + + def __repr__(self): + return "File(" + self.__str__() + ")" + + def check_if_exists(self): + return os.path.exists(self.dst) + + def move(self): + if self.check_if_exists(): # for overwriting + os.remove(self.dst) + if not os.path.exists(os.path.dirname(self.dst)): + os.makedirs(os.path.dirname(self.dst)) + shutil.move(self.src, self.dst, copy_function=partial(shutil.copy2, follow_symlinks=False)) + + def copy(self): + if self.check_if_exists(): # for overwriting + os.remove(self.dst) + if not os.path.exists(os.path.dirname(self.dst)): + os.makedirs(os.path.dirname(self.dst)) + shutil.copy2(self.src, self.dst, follow_symlinks=False) + +def prompt_user(message, wants_response, conflicts = None, choices = None, detail = None): payload = { "wants-response": wants_response, "message": message @@ -37,6 +65,8 @@ def prompt_user(message, wants_response, conflicts = None, choices = None): payload["conflicts"] = conflicts if choices != None: payload["choices"] = choices + if detail != None: + payload["detail"] = detail print(json.dumps(payload) + "\n") if wants_response: response = json.loads(input()) @@ -45,78 +75,99 @@ def prompt_user(message, wants_response, conflicts = None, choices = None): return response return -def split_paths_at_cwd(paths, cwd): - response = [] - for path in paths: - response.append(cwd + "/./" + os.path.relpath(path, cwd)) - return response - -def recursive_get_conflicts(input_array, cwd, dest): +def get_conflicts(files): conflicts = [] non_conflicts = [] - for source in input_array: - if os.path.isdir(source): - child_nodes = os.listdir(source) - child_paths = [] - for node in child_nodes: - child_paths.append(source + "/" + node) - (more_conflicts, more_non_conflicts) = recursive_get_conflicts(child_paths, cwd, dest) - conflicts += more_conflicts - non_conflicts += more_non_conflicts - continue - dest_path = dest + "/" + os.path.relpath(source, cwd) - if os.path.exists(dest_path): - conflicts.append((source, dest_path)) + for file in files: + if file.check_if_exists(): + conflicts.append(file) else: - non_conflicts.append(source) + non_conflicts.append(file) return (conflicts, non_conflicts) -def filter_existing(args, cwd): - sources = args[:-1] - dest = args[-1] - (conflicts, non_conflicts) = recursive_get_conflicts(sources, cwd, dest) +def filter_existing(files): + (conflicts, non_conflicts) = get_conflicts(files) if len(conflicts): - choice = prompt_user("Conflicts Found", True, choices=[("skip-conflicts", "Skip Conflicts"), ("select", "Overwrite Selectively"), ("overwrite", "Overwrite All")]) + choice = prompt_user("Conflicts Found", True, choices=[("skip-conflicts", "Skip Conflicts"), ("rename", "Append Number to Conflicting Names"), ("select", "Overwrite Selectively"), ("overwrite", "Overwrite All")]) if choice == "overwrite": - non_conflicts.extend(map(lambda x: x[0], conflicts)) + non_conflicts.extend(conflicts) elif choice == "select": - conflicts = prompt_user("Overwrite?", True, conflicts) + prompts = list(map(lambda f: [f.src, f.dst], conflicts)) # get list of [src, dst] + keeper_lut = prompt_user("Overwrite?", True, conflicts=prompts) # returns dict of srcs : keep (bool) + non_conflicts.extend(filter(lambda f: keeper_lut[f.src], conflicts)) + elif choice == "rename": + for conflict in conflicts: + num = 1 + conflict.dst += "(1)" + while conflict.check_if_exists(): + num += 1 + conflict.dst = re.sub(r'\(\d+\)$', f"({num})", conflict.dst) non_conflicts.extend(conflicts) elif choice == "skip-conflicts": pass else: # cancelled sys.exit(0) - if not len(non_conflicts): - sys.exit(0) # exit if nothing to copy - filtered_args = [*split_paths_at_cwd(non_conflicts, cwd), dest] - return filtered_args - -def paste(cmd, args): - try: - child = subprocess.Popen( - [*cmd, *args], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True - ) - except Exception as e: - prompt_user(str(e), False) - sys.exit(1) - child.wait() - if child.returncode: - stdout, stderr = child.communicate() - prompt_user(stdout + stderr, False) - sys.exit(child.returncode) + + return non_conflicts +def recursive_get_files(cwd, dest_path, source_directory): + files = [] + directories_to_remove = [] + for entry in os.listdir(source_directory): + path = source_directory + "/" + entry + if os.path.isdir(path): + (new_files, new_directories) = recursive_get_files(cwd, dest_path, path) + files.extend(new_files) + directories_to_remove.extend(new_directories) + directories_to_remove.append(path) + else: + files.append(File(path, cwd, dest_path)) + return (files, directories_to_remove) def main(): parser = OptionParser() parser.add_option("-m", "--move", help="remove source files", action="store_true", dest="move", default=False) (options, args) = parser.parse_args() cwd = args[0] - filtered_args = filter_existing(args[1:], cwd) + sources = args[1:-1] + dest_path = args[-1] + files = [] + directories_to_remove = [] + for source_path in sources: + if os.path.isdir(source_path): + (new_files, new_directories) = recursive_get_files(cwd, dest_path, source_path) + files.extend(new_files) + directories_to_remove.extend(new_directories) + directories_to_remove.append(source_path) + elif os.path.exists(source_path): + files.append(File(source_path, cwd, dest_path)) if options.move: - paste(["rsync", "-aI", "--relative", "--remove-source-files"], filtered_args) + files = filter(lambda f: f.src != f.dst, files) + files = filter_existing(files) + if not len(files): + sys.exit(0) # exit if nothing to copy + if options.move: + for file in files: + try: + file.move() + except Exception as e: + prompt_user("Failed to move " + os.path.relpath(file.src, cwd), wants_response=False, detail=str(e)) + for directory in directories_to_remove: + try: + os.rmdir(directory) + except OSError as e: + if e.errno == errno.ENOTEMPTY: + pass # skip deletion + else: + prompt_user("Failed to remove directory " + directory, wants_response=False, detail=str(e)) + except Exception as e: + prompt_user("Failed to remove directory " + directory, wants_response=False, detail=str(e)) else: - paste(["rsync", "-aI", "--relative"], filtered_args) + for file in files: + try: + file.copy() + except Exception as e: + prompt_user("Failed to copy " + os.path.relpath(file.src, cwd), wants_response=False, detail=str(e)) sys.exit(0)