Merge branch 'dev-josh-paste-overhaul' into dev-josh

This commit is contained in:
joshuaboud 2021-11-29 12:59:37 -04:00
commit dabffa2853
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

@ -22,19 +22,49 @@ 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:
def __init__(self, path, cwd, dest_path):
self.src = path
self.dst = dest_path + "/" + os.path.relpath(path, cwd)
def __str__(self):
return self.src + " -> " + 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, detail = None):
payload = { payload = {
"wants-response": wants_response, "wants-response": wants_response,
"message": message "message": message
} }
if conflicts != None: if conflicts != None:
payload["conflicts"] = conflicts payload["conflicts"] = conflicts
if detail != None:
payload["detail"] = detail
print(json.dumps(payload) + "\n") print(json.dumps(payload) + "\n")
if wants_response: if wants_response:
response = json.loads(input()) response = json.loads(input())
@ -43,70 +73,82 @@ def prompt_user(message, wants_response, conflicts = None):
return response return response
return return
def split_paths_at_cwd(paths, cwd): def get_conflicts(files):
response = []
for path in paths:
response.append(cwd + "/./" + os.path.relpath(path, cwd))
return response
def recursive_get_conflicts(input_array, cwd, dest):
conflicts = [] conflicts = []
non_conflicts = [] non_conflicts = []
for source in input_array: for file in files:
if os.path.isdir(source): if file.check_if_exists():
child_nodes = os.listdir(source) conflicts.append(file)
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: else:
non_conflicts.append(source) non_conflicts.append(file)
return (conflicts, non_conflicts) return (conflicts, non_conflicts)
def filter_existing(args, cwd): def filter_existing(files):
sources = args[:-1] (conflicts, non_conflicts) = get_conflicts(files)
dest = args[-1]
(conflicts, non_conflicts) = recursive_get_conflicts(sources, cwd, dest)
if len(conflicts): if len(conflicts):
conflicts = prompt_user("Overwrite?", True, conflicts) prompts = list(map(lambda f: [f.src, f.dst], conflicts)) # get list of [src, dst]
non_conflicts.extend(conflicts) keeper_lut = prompt_user("Overwrite?", True, prompts) # returns dict of srcs : keep (bool)
if not len(non_conflicts): non_conflicts.extend(filter(lambda f: keeper_lut[f.src], conflicts))
sys.exit(0) # exit if nothing to copy return non_conflicts
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 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]
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))
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: if options.move:
paste(["rsync", "-aI", "--relative", "--remove-source-files"], filtered_args) 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: else:
paste(["rsync", "-aI", "--relative"], filtered_args) 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) sys.exit(0)