Greatly improved file handling flow and added popup guards.

This commit is contained in:
2021-12-05 21:18:39 -06:00
parent cbf5706845
commit bed8af256c
9 changed files with 482 additions and 162 deletions

View File

@@ -44,7 +44,7 @@ class Launcher:
os.system(command)
else:
DEVNULL = open(os.devnull, 'w')
subprocess.Popen(command, cwd=start_dir, shell=False, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True)
subprocess.Popen(command, cwd=start_dir, shell=True, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True)
def remux_video(self, hash, file):

View File

@@ -127,7 +127,6 @@ class Controller(Controller_Data, ShowHideMixin, KeyboardSignalsMixin, \
if action == "execute_in_terminal":
self.execute_files(in_terminal=True)
if action == "rename":
self.to_rename_files = self.selected_files
self.rename_files()
if action == "cut":
self.to_copy_files.clear()

View File

@@ -31,10 +31,11 @@ class Controller_Data:
self.arc_command_buffer = self.builder.get_object("arc_command_buffer")
self.warning_alert = self.builder.get_object("warning_alert")
self.exists_alert = self.builder.get_object("exists_alert")
self.exists_from_label = self.builder.get_object("exists_from_label")
self.exists_to_label = self.builder.get_object("exists_to_label")
self.edit_file_menu = self.builder.get_object("edit_file_menu")
self.file_exists_dialog = self.builder.get_object("file_exists_dialog")
self.exists_file_label = self.builder.get_object("exists_file_label")
self.exists_file_field = self.builder.get_object("exists_file_field")
self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn")
self.bottom_size_label = self.builder.get_object("bottom_size_label")
self.bottom_file_count_label = self.builder.get_object("bottom_file_count_label")
@@ -72,7 +73,6 @@ class Controller_Data:
self.notebooks = [self.window1, self.window2, self.window3, self.window4]
self.selected_files = []
self.to_rename_files = []
self.to_copy_files = []
self.to_cut_files = []

View File

@@ -73,7 +73,7 @@ class KeyboardSignalsMixin:
if keyname == "delete":
self.delete_files()
if keyname == "f2":
self.do_edit_files()
self.rename_files()
if keyname == "f4":
wid, tid = self.window_controller.get_active_data()
view = self.get_fm_window(wid).get_view_by_id(tid)

View File

@@ -15,29 +15,32 @@ class ShowHideMixin:
def show_exists_page(self, widget=None, eve=None):
# self.exists_alert = self.builder.get_object("exists_alert")
# self.exists_from_label = self.builder.get_object("exists_from_label")
# self.exists_to_label = self.builder.get_object("exists_to_label")
# self.exists_file_field = self.builder.get_object("exists_file_field")
response = self.file_exists_dialog.run()
self.file_exists_dialog.hide()
if response == Gtk.ResponseType.OK:
return "rename"
if response == Gtk.ResponseType.ACCEPT:
return "rename_auto"
if response == Gtk.ResponseType.CLOSE:
return "rename_auto_all"
if response == Gtk.ResponseType.YES:
return "overwrite"
if response == Gtk.ResponseType.APPLY:
return "overwrite_all"
if response == Gtk.ResponseType.NO:
return "skip"
if response == Gtk.ResponseType.REJECT:
return "skip_all"
response = self.exists_alert.run()
if response == Gtk.ResponseType.OK: # Rename
print(response)
return "rename", Gio.FileCreateFlags.NONE
if response == Gtk.ResponseType.ACCEPT: # Auto rename
return "rename_auto", Gio.FileCreateFlags.NONE
if response == Gtk.ResponseType.CLOSE: # Auto rename all
return "rename_auto_all", Gio.FileCreateFlags.NONE
if response == Gtk.ResponseType.YES: # Overwrite
return "overwrite", Gio.FileCreateFlags.OVERWRITE
if response == Gtk.ResponseType.APPLY: # Overwrite all
return "overwrite_all", Gio.FileCreateFlags.OVERWRITE
if response == Gtk.ResponseType.NO: # Skip
return "skip", Gio.FileCreateFlags.NONE
if response == Gtk.ResponseType.CANCEL: # Skip all
return "skip_all", Gio.FileCreateFlags.NONE
def hide_exists_page_rename(self, widget=None, eve=None):
self.file_exists_dialog.response(Gtk.ResponseType.OK)
def hide_exists_page_auto_rename(self, widget=None, eve=None):
self.file_exists_dialog.response(Gtk.ResponseType.ACCEPT)
def hide_exists_page_auto_rename_all(self, widget=None, eve=None):
self.file_exists_dialog.response(Gtk.ResponseType.CLOSE)
def show_about_page(self, widget=None, eve=None):
@@ -49,6 +52,7 @@ class ShowHideMixin:
def hide_about_page(self, widget=None, eve=None):
self.builder.get_object("about_page").hide()
def show_archiver_dialogue(self, widget=None, eve=None):
wid, tid = self.window_controller.get_active_data()
view = self.get_fm_window(wid).get_view_by_id(tid)
@@ -68,6 +72,7 @@ class ShowHideMixin:
def hide_archiver_dialogue(self, widget=None, eve=None):
self.builder.get_object("archiver_dialogue").hide()
def show_appchooser_menu(self, widget=None, eve=None):
appchooser_menu = self.builder.get_object("appchooser_menu")
appchooser_widget = self.builder.get_object("appchooser_widget")
@@ -86,36 +91,38 @@ class ShowHideMixin:
dialog = widget.get_parent().get_parent()
dialog.response(Gtk.ResponseType.OK)
def show_context_menu(self, widget=None, eve=None):
self.builder.get_object("context_menu").run()
def hide_context_menu(self, widget=None, eve=None):
self.builder.get_object("context_menu").hide()
def show_new_file_menu(self, widget=None, eve=None):
self.builder.get_object("new_file_menu").run()
def hide_new_file_menu(self, widget=None, eve=None):
self.builder.get_object("new_file_menu").hide()
def show_edit_file_menu(self, widget=None, eve=None):
self.builder.get_object("edit_file_menu").run()
response = self.edit_file_menu.run()
if response == Gtk.ResponseType.CLOSE:
self.skip_edit = True
if response == Gtk.ResponseType.CANCEL:
self.cancel_edit = True
def hide_edit_file_menu(self, widget=None, eve=None):
if widget:
name = widget.get_name()
if name == "rename":
self.builder.get_object("edit_file_menu").hide()
else:
keyname = Gdk.keyval_name(eve.keyval).lower()
if "return" in keyname or "enter" in keyname:
self.builder.get_object("edit_file_menu").hide()
self.builder.get_object("edit_file_menu").hide()
def hide_edit_file_menu_enter_key(self, widget=None, eve=None):
keyname = Gdk.keyval_name(eve.keyval).lower()
if "return" in keyname or "enter" in keyname:
self.builder.get_object("edit_file_menu").hide()
def hide_edit_file_menu_skip(self, widget=None, eve=None):
self.skip_edit = True
self.builder.get_object("edit_file_menu").hide()
self.edit_file_menu.response(Gtk.ResponseType.CLOSE)
def hide_edit_file_menu_cancel(self, widget=None, eve=None):
self.cancel_edit = True
self.builder.get_object("edit_file_menu").hide()
self.edit_file_menu.response(Gtk.ResponseType.CANCEL)

View File

@@ -1,5 +1,5 @@
# Python imports
import os
import threading, os
# Lib imports
import gi
@@ -10,6 +10,12 @@ from gi.repository import Gtk, GObject, Gio
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs).start()
return wrapper
class WidgetFileActionMixin:
def set_file_watcher(self, view):
if view.get_dir_watcher():
@@ -69,13 +75,18 @@ class WidgetFileActionMixin:
for file in uris:
view.open_file_locally(file)
@threaded
def open_with_files(self, appchooser_widget):
wid, tid, view, iconview, store = self.get_current_state()
uris = self.format_to_uris(store, wid, tid, self.selected_files)
file = Gio.File.new_for_uri(uris[0])
app_info = appchooser_widget.get_app_info()
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
files = []
app_info.launch([file], None)
for uri in uris:
gio_file = Gio.File.new_for_path(uri)
files.append(gio_file)
app_info.launch(files, None)
def execute_files(self, in_terminal=False):
wid, tid, view, iconview, store = self.get_current_state()
@@ -85,64 +96,58 @@ class WidgetFileActionMixin:
for path in paths:
command = f"sh -c '{path}'" if not in_terminal else f"{view.terminal_app} -e '{path}'"
view.execute(command.split(), current_dir)
view.execute(command, start_dir=view.get_current_directory(), use_os_system=False)
def archive_files(self, archiver_dialogue):
wid, tid, view, iconview, store = self.get_current_state()
paths = self.format_to_uris(store, wid, tid, self.selected_files, True)
save_target = archiver_dialogue.get_filename();
sItr, eItr = self.arc_command_buffer.get_bounds()
pre_command = self.arc_command_buffer.get_text(sItr, eItr, False)
pre_command = pre_command.replace("%o", save_target)
pre_command = pre_command.replace("%N", ' '.join(paths))
command = f"{view.terminal_app} -e '{command}'"
##################################################################################################
# NOTE: Everything below is trash and needs yet another rewrite because it doesn't work properly.
##################################################################################################
view.execute(command, start_dir=None, use_os_system=True)
def rename_files(self):
rename_label = self.builder.get_object("file_to_rename_label")
rename_input = self.builder.get_object("new_rename_fname")
wid, tid, view, iconview, store = self.get_current_state()
uris = self.format_to_uris(store, wid, tid, self.to_rename_files)
uris = self.format_to_uris(store, wid, tid, self.selected_files)
# The rename button hides the rename dialog box which lets the loop continue.
# Weirdly, the show at the end is needed to flow through all the list properly
# than auto chosing the first rename entry you do.
for uri in uris:
entry = uri.split("/")[-1]
rename_label.set_label(entry)
rename_input.set_text(entry)
if self.skip_edit:
self.skip_edit = False
self.show_edit_file_menu()
# Yes...this step is required even with the above... =/
self.show_edit_file_menu()
if self.skip_edit:
self.skip_edit = False
continue
if self.cancel_edit:
self.cancel_edit = False
break
rname_to = rename_input.get_text().strip()
target = f"file://{view.get_current_directory()}/{rname_to}"
self.handle_file([uri], "edit", target)
self.handle_file([uri], "rename", target)
self.show_edit_file_menu()
self.skip_edit = False
self.cancel_edit = False
self.hide_new_file_menu()
self.to_rename_files.clear()
self.hide_edit_file_menu()
self.selected_files.clear()
def cut_files(self):
wid, tid, view, iconview, store = self.get_current_state()
uris = self.format_to_uris(store, wid, tid, self.selected_files)
uris = self.format_to_uris(store, wid, tid, self.selected_files)
self.to_cut_files = uris
def copy_files(self):
wid, tid, view, iconview, store = self.get_current_state()
print(self.selected_files)
uris = self.format_to_uris(store, wid, tid, self.selected_files)
print(uris)
uris = self.format_to_uris(store, wid, tid, self.selected_files)
self.to_copy_files = uris
def paste_files(self):
@@ -155,28 +160,35 @@ class WidgetFileActionMixin:
elif len(self.to_cut_files) > 0:
self.handle_file(self.to_cut_files, "move", target)
def archive_files(self, archiver_dialogue):
wid, tid, view, iconview, store = self.get_current_state()
paths = self.format_to_uris(store, wid, tid, self.selected_files)
save_target = archiver_dialogue.get_filename();
start_itr, end_itr = self.arc_command_buffer.get_bounds()
command = self.arc_command_buffer.get_text(start_itr, end_itr, False)
command = command.replace("%o", save_target)
command = command.replace("%N", ' '.join(paths))
final_command = f"terminator -e '{command}'"
self.execute(final_command, start_dir=None, use_os_system=True)
def delete_files(self):
wid, tid, view, iconview, store = self.get_current_state()
uris = self.format_to_uris(store, wid, tid, self.selected_files)
self.handle_file(uris, "delete")
uris = self.format_to_uris(store, wid, tid, self.selected_files)
response = None
self.warning_alert.format_secondary_text(f"Do you really want to delete the {len(uris)} file(s)?")
for uri in uris:
file = Gio.File.new_for_uri(uri)
if not response:
response = self.warning_alert.run()
self.warning_alert.hide()
if response == Gtk.ResponseType.YES:
type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
if type == Gio.FileType.DIRECTORY:
view.delete_file( file.get_path() )
else:
file.delete(cancellable=None)
else:
break
def trash_files(self):
wid, tid, view, iconview, store = self.get_current_state()
uris = self.format_to_uris(store, wid, tid, self.selected_files)
self.handle_file(uris, "trash")
for uri in uris:
file = Gio.File.new_for_uri(uri)
file.trash(cancellable=None)
@@ -191,104 +203,146 @@ class WidgetFileActionMixin:
target = f"{view.get_current_directory()}"
if file_name:
file_name = "file://" + target + "/" + file_name
path = "file://{target}/{file_name}"
if type == True: # Create File
self.handle_file([file_name], "create_file")
self.handle_file([path], "create_file")
else: # Create Folder
self.handle_file([file_name], "create_dir")
self.handle_file([path], "create_dir")
fname_field.set_text("")
def move_files(self, files, target):
self.handle_file(files, "move", target)
# NOTE: While not fully race condition proof, we happy path it first
# and then handle anything after as a conflict for renaming before
# copy, move, or edit. This is literally the oppopsite of what Gtk says to do.
# But, they can't even delete directories properly. So... f**k them.
# NOTE: Gtk recommends using fail flow than pre check existence which is more
# race condition proof. They're right; but, they can't even delete
# directories properly. So... f**k them. I'll do it my way.
def handle_file(self, paths, action, _target_path=None):
paths = self.preprocess_paths(paths)
target = None
response = None
self.warning_alert.format_secondary_text(f"Do you really want to {action} the {len(paths)} file(s)?")
target = None
_file = None
response = None
overwrite_all = False
rename_auto_all = False
for path in paths:
try:
file = Gio.File.new_for_uri(path)
if action == "trash":
file.trash(cancellable=None)
if (action == "create_file" or action == "create_dir") and not file.query_exists():
if action == "create_file":
file.create(flags=Gio.FileCreateFlags.NONE, cancellable=None)
continue
if action == "create_dir":
file.make_directory(cancellable=None)
continue
if _target_path:
if os.path.isdir(_target_path.split("file://")[1]):
info = file.query_info("standard::display-name", 0, cancellable=None)
_target = f"{_target_path}/{info.get_display_name()}"
target = Gio.File.new_for_uri(_target)
_file = Gio.File.new_for_uri(_target)
else:
target = Gio.File.new_for_uri(_target_path)
if target and not target.query_exists():
type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
if type == Gio.FileType.DIRECTORY:
wid, tid = self.window_controller.get_active_data()
view = self.get_fm_window(wid).get_view_by_id(tid)
fPath = file.get_path()
tPath = None
state = True
if action == "copy":
tPath = target.get_path()
view.copy_file(fPath, tPath)
if action == "move" or action == "edit":
tPath = target.get_parent().get_path()
view.move_file(fPath, tPath)
else:
if action == "copy":
file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None)
if action == "move" or action == "edit":
file.move(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None)
_file = Gio.File.new_for_uri(_target_path)
else:
_file = Gio.File.new_for_uri(path)
if _file.query_exists():
if not overwrite_all and not rename_auto_all:
self.exists_file_label.set_label(_file.get_basename())
self.exists_file_field.set_text(_file.get_basename())
response = self.show_exists_page()
if response == "overwrite_all":
overwrite_all = True
if response == "rename_auto_all":
rename_auto_all = True
# NOTE: Past here, we need to handle detected conflicts.
# Maybe create a collection of file and target pares
# that then get passed to a handler who calls show_exists_page?
if response == "rename":
base_path = _file.get_parent().get_path()
new_name = self.exists_file_field.get_text().strip()
rfPath = f"{base_path}/{new_name}"
_file = Gio.File.new_for_path(rfPath)
if action == "delete":
if not response:
response = self.warning_alert.run()
self.warning_alert.hide()
if response == Gtk.ResponseType.YES:
type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
if response == "rename_auto" or rename_auto_all:
_file = self.rename_proc(_file)
if response == "overwrite" or overwrite_all:
type = _file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
if type == Gio.FileType.DIRECTORY:
wid, tid = self.window_controller.get_active_data()
view = self.get_fm_window(wid).get_view_by_id(tid)
view.delete_file( file.get_path() )
view.delete_file( _file.get_path() )
else:
file.delete(cancellable=None)
else:
_file.delete(cancellable=None)
if response == "skip":
continue
if response == "skip_all":
break
if _target_path:
target = _file
else:
file = _file
if action == "create_file":
file.create(flags=Gio.FileCreateFlags.NONE, cancellable=None)
continue
if action == "create_dir":
file.make_directory(cancellable=None)
continue
type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
if type == Gio.FileType.DIRECTORY:
wid, tid = self.window_controller.get_active_data()
view = self.get_fm_window(wid).get_view_by_id(tid)
fPath = file.get_path()
tPath = None
state = True
if action == "copy":
tPath = target.get_path()
view.copy_file(fPath, tPath)
if action == "move" or action == "rename":
tPath = target.get_parent().get_path()
view.move_file(fPath, tPath)
else:
if action == "copy":
file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None)
if action == "move" or action == "rename":
file.move(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None)
except GObject.GError as e:
raise OSError(e)
self.exists_file_rename_bttn.set_sensitive(False)
def preprocess_paths(self, paths):
if not isinstance(paths, list):
paths = [paths]
# Convert items such as pathlib paths to strings
paths = [path.__fspath__() if hasattr(path, "__fspath__") else path for path in paths]
return paths
def rename_proc(self, gio_file):
full_path = gio_file.get_path()
base_path = gio_file.get_parent().get_path()
file_name = os.path.splitext(gio_file.get_basename())[0]
extension = os.path.splitext(full_path)[-1]
target = Gio.File.new_for_path(full_path)
if debug:
print(f"Path: {full_path}")
print(f"Base Path: {base_path}")
print(f'Name: {file_name}')
print(f"Extension: {extension}")
i = 2
while target.query_exists():
target = Gio.File.new_for_path(f"{base_path}/{file_name}-copy{i}{extension}")
i += 1
return target
def exists_rename_field_changed(self, widget):
nfile_name = widget.get_text().strip()
ofile_name = self.exists_file_label.get_label()
if nfile_name:
if nfile_name == ofile_name:
self.exists_file_rename_bttn.set_sensitive(False)
else:
self.exists_file_rename_bttn.set_sensitive(True)
else:
self.exists_file_rename_bttn.set_sensitive(False)