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

This commit is contained in:
joshuaboud 2021-12-16 11:22:10 -04:00
commit 3d89accd7c
No known key found for this signature in database
GPG Key ID: 17EFB59E2A8BF50E
2 changed files with 108 additions and 54 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(
@ -700,13 +701,15 @@ export class NavWindow {
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,13 +22,41 @@ 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, re
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, choices = 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, choices = None, detail = None):
payload = { payload = {
"wants-response": wants_response, "wants-response": wants_response,
"message": message "message": message
@ -37,6 +65,8 @@ def prompt_user(message, wants_response, conflicts = None, choices = None):
payload["conflicts"] = conflicts payload["conflicts"] = conflicts
if choices != None: if choices != None:
payload["choices"] = choices payload["choices"] = choices
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())
@ -45,78 +75,99 @@ def prompt_user(message, wants_response, conflicts = None, choices = 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):
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": if choice == "overwrite":
non_conflicts.extend(map(lambda x: x[0], conflicts)) non_conflicts.extend(conflicts)
elif choice == "select": 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) non_conflicts.extend(conflicts)
elif choice == "skip-conflicts": elif choice == "skip-conflicts":
pass pass
else: # cancelled else: # cancelled
sys.exit(0) sys.exit(0)
if not len(non_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))
if options.move: 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: 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) sys.exit(0)