start overhauling paste

This commit is contained in:
joshuaboud 2021-11-29 11:57:43 -04:00
parent 328caac7d2
commit 9d1d4eea8a
No known key found for this signature in database
GPG Key ID: 17EFB59E2A8BF50E
2 changed files with 137 additions and 97 deletions

View File

@ -645,6 +645,7 @@ export class NavWindow {
cmd.push(item.path_str()); cmd.push(item.path_str());
} }
cmd.push(dest); cmd.push(dest);
console.log(cmd);
this.clip_board.length = 0; // clear clipboard this.clip_board.length = 0; // clear clipboard
var promise = new Promise((resolve, reject) => { var promise = new Promise((resolve, reject) => {
var proc = cockpit.spawn( var proc = cockpit.spawn(
@ -670,24 +671,21 @@ export class NavWindow {
proc.input(JSON.stringify("abort") + "\n"); proc.input(JSON.stringify("abort") + "\n");
return; return;
} }
let keepers = []; proc.input(JSON.stringify(responses) + "\n", true);
for (let response of Object.keys(responses)) {
if (responses[response])
keepers.push(response)
}
proc.input(JSON.stringify(keepers) + "\n", true);
} else { } else {
var user_response = await this.modal_prompt.confirm(payload["message"]); var user_response = await this.modal_prompt.confirm(payload["message"]);
proc.input(JSON.stringify(user_response) + "\n", true); proc.input(JSON.stringify(user_response) + "\n", true);
} }
} else { } else {
await this.modal_prompt.alert(payload["message"]); await this.modal_prompt.alert(payload["message"], payload?.detail);
} }
}); });
proc.done((data) => { proc.done((data) => {
resolve(); resolve();
}); });
proc.fail((e, data) => { proc.fail((e, data) => {
console.log(e);
console.log(data);
reject("Paste failed."); reject("Paste failed.");
}); });
}); });

View File

@ -1,20 +1,20 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Cockpit Navigator - A File System Browser for Cockpit. Cockpit Navigator - A File System Browser for Cockpit.
Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com> Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator. This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful, Cockpit Navigator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>. along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
""" """
""" """
@ -22,93 +22,135 @@ Synopsis: `paste.py3 [-m] <cwd of copy> <list of source files> <destination dire
all full paths all full paths
""" """
import os from functools import partial
import os, shutil, errno
import sys import sys
from optparse import OptionParser from optparse import OptionParser
import json import json
import subprocess
def prompt_user(message, wants_response, conflicts = None): class File:
payload = { def __init__(self, path, cwd, dest_path):
"wants-response": wants_response, self.src = path
"message": message self.dst = dest_path + "/" + os.path.relpath(path, cwd)
}
if conflicts != None:
payload["conflicts"] = conflicts
print(json.dumps(payload) + "\n")
if wants_response:
response = json.loads(input())
if isinstance(response, str) and response == "abort":
sys.exit(0)
return response
return
def split_paths_at_cwd(paths, cwd): def __str__(self):
response = [] return self.src + " -> " + self.dst
for path in paths:
response.append(cwd + "/./" + os.path.relpath(path, cwd))
return response
def recursive_get_conflicts(input_array, cwd, dest): def __repr__(self):
conflicts = [] return "File(" + self.__str__() + ")"
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): def check_if_exists(self):
sources = args[:-1] return os.path.exists(self.dst)
dest = args[-1]
(conflicts, non_conflicts) = recursive_get_conflicts(sources, cwd, dest)
if len(conflicts):
conflicts = prompt_user("Overwrite?", True, conflicts)
non_conflicts.extend(conflicts)
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): def move(self):
try: if self.check_if_exists(): # for overwriting
child = subprocess.Popen( os.remove(self.dst)
[*cmd, *args], if not os.path.exists(os.path.dirname(self.dst)):
stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True os.makedirs(os.path.dirname(self.dst))
) shutil.move(self.src, self.dst, copy_function=partial(shutil.copy2, follow_symlinks=False))
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 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, detail = None):
payload = {
"wants-response": wants_response,
"message": message
}
if conflicts != None:
payload["conflicts"] = conflicts
if detail != None:
payload["detail"] = detail
print(json.dumps(payload) + "\n")
if wants_response:
response = json.loads(input())
if isinstance(response, str) and response == "abort":
sys.exit(0)
return response
return
def get_conflicts(files):
conflicts = []
non_conflicts = []
for file in files:
if file.check_if_exists():
conflicts.append(file)
else:
non_conflicts.append(file)
return (conflicts, non_conflicts)
def filter_existing(files):
(conflicts, non_conflicts) = get_conflicts(files)
if len(conflicts):
prompts = list(map(lambda f: [f.src, f.dst], conflicts)) # get list of [src, dst]
keeper_lut = prompt_user("Overwrite?", True, prompts) # returns dict of srcs : keep (bool)
non_conflicts.extend(filter(lambda f: keeper_lut[f.src], conflicts))
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(): def main():
parser = OptionParser() parser = OptionParser()
parser.add_option("-m", "--move", help="remove source files", action="store_true", dest="move", default=False) parser.add_option("-m", "--move", help="remove source files", action="store_true", dest="move", default=False)
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
cwd = args[0] cwd = args[0]
filtered_args = filter_existing(args[1:], cwd) sources = args[1:-1]
if options.move: dest_path = args[-1]
paste(["rsync", "-aI", "--relative", "--remove-source-files"], filtered_args) files = []
else: directories_to_remove = []
paste(["rsync", "-aI", "--relative"], filtered_args) for source_path in sources:
sys.exit(0) 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))
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:
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))
prompt_user("Directories to remove:\n" + "\n".join(directories_to_remove), False)
sys.exit(0)
if __name__ == "__main__": if __name__ == "__main__":
main() main()