From dc2ecb03b12d407f423dbbb75e5fbfe59d3376a4 Mon Sep 17 00:00:00 2001 From: joshuaboud Date: Wed, 2 Jun 2021 11:13:17 -0300 Subject: [PATCH] helper script to wrap rsync for pasting --- navigator/navigator.js | 38 +++++++------ navigator/scripts/paste.py | 114 +++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 16 deletions(-) create mode 100755 navigator/scripts/paste.py diff --git a/navigator/navigator.js b/navigator/navigator.js index a8a7703..fafc4bb 100644 --- a/navigator/navigator.js +++ b/navigator/navigator.js @@ -753,14 +753,16 @@ class NavContextMenu { copy() { this.nav_window_ref.clip_board = [...this.nav_window_ref.selected_entries]; - this.menu_options["paste"].hidden = false; this.nav_window_ref.copy_or_move = "copy"; + this.nav_window_ref.paste_cwd = this.nav_window_ref.pwd().path_str(); + this.menu_options["paste"].hidden = false; } move() { this.nav_window_ref.clip_board = [...this.nav_window_ref.selected_entries]; - this.menu_options["paste"].hidden = false; this.nav_window_ref.copy_or_move = "move"; + this.nav_window_ref.paste_cwd = this.nav_window_ref.pwd().path_str(); + this.menu_options["paste"].hidden = false; } delete() { @@ -1197,32 +1199,36 @@ class NavWindow { } async paste_clipboard() { + this.start_load(); this.context_menu.hide_paste(); - var cmd = []; + var cmd = ["/usr/share/cockpit/navigator/scripts/paste.py"]; var dest = this.pwd().path_str(); - switch (this.copy_or_move) { - case "copy": - cmd = ["cp", "-an"]; - break; - case "move": - cmd = ["mv", "-n"]; - break; - default: - return; + if (this.copy_or_move === "move") { + cmd.push("-m"); } + cmd.push(this.paste_cwd); for (let item of this.clip_board) { cmd.push(item.path_str()); } cmd.push(dest); - console.log(cmd); var proc = cockpit.spawn( cmd, - {superuser: "try", err: "out"} + {superuser: "try", err: "ignore"} ); + proc.stream((data) => { + var payload = JSON.parse(data); + if (payload["wants-response"]) { + var user_response = window.confirm(payload["message"]); + proc.input(JSON.stringify(user_response) + "\n", true); + } else { + window.alert(payload["message"]); + } + }); proc.fail((e, data) => { - window.alert(data); - }) + window.alert("Paste failed."); + }); await proc; + this.stop_load(); this.refresh(); } diff --git a/navigator/scripts/paste.py b/navigator/scripts/paste.py new file mode 100755 index 0000000..a832c34 --- /dev/null +++ b/navigator/scripts/paste.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 + +""" + Cockpit Navigator - A File System Browser for Cockpit. + Copyright (C) 2021 Josh Boudreau + + This file is part of Cockpit Navigator. + Cockpit Navigator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Cockpit Navigator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Cockpit Navigator. If not, see . +""" + +""" +Synopsis: `paste.py [-m] ` +all full paths +""" + +import os +import sys +from optparse import OptionParser +import json +import subprocess + +def prompt_user(message, wants_response): + payload = { + "wants-response": wants_response, + "message": message + } + print(json.dumps(payload) + "\n") + if wants_response: + response = input() + return json.loads(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): + 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)) + else: + non_conflicts.append(source) + return (conflicts, non_conflicts) + +def filter_existing(args, cwd): + sources = args[:-1] + dest = args[-1] + (conflicts, non_conflicts) = recursive_get_conflicts(sources, cwd, dest) + if len(conflicts): + check_continue = prompt_user("Conflicts were found while pasting. `Cancel` to abort operation, `OK` to overwrite selectively.", True) + if not check_continue: + sys.exit(0) + for conflict in conflicts: + if prompt_user("Overwrite " + conflict[1] + "?", True): + non_conflicts.append(conflict[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) + + +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) + if options.move: + paste(["rsync", "-aI", "--relative", "--remove-source-files"], filtered_args) + else: + paste(["rsync", "-aI", "--relative"], filtered_args) + sys.exit(0) + + +if __name__ == "__main__": + main() \ No newline at end of file