develop #11
| @@ -113,7 +113,7 @@ class Plugin(PluginBase): | ||||
|         self._archiver_dialogue.hide() | ||||
|  | ||||
|     def archive_files(self, save_target, state): | ||||
|         paths       = [shlex.quote(p) for p in state.selected_files] | ||||
|         paths       = [shlex.quote(p) for p in state.uris] | ||||
|  | ||||
|         sItr, eItr  = self._arc_command_buffer.get_bounds() | ||||
|         pre_command = self._arc_command_buffer.get_text(sItr, eItr, False) | ||||
|   | ||||
| @@ -123,8 +123,8 @@ class Plugin(PluginBase): | ||||
|         GLib.idle_add(self._process_changes, (state)) | ||||
|  | ||||
|     def _process_changes(self, state): | ||||
|         if len(state.selected_files) == 1: | ||||
|             uri  = state.selected_files[0] | ||||
|         if len(state.uris) == 1: | ||||
|             uri  = state.uris[0] | ||||
|             path = state.tab.get_current_directory() | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -95,7 +95,7 @@ class Plugin(PluginBase): | ||||
|     def _process_changes(self, state): | ||||
|         self._fm_state = None | ||||
|  | ||||
|         if len(state.selected_files) == 1: | ||||
|         if len(state.uris) == 1: | ||||
|             self._fm_state = state | ||||
|             self._set_ui_data() | ||||
|             response   = self._thumbnailer_dialog.run() | ||||
| @@ -115,7 +115,7 @@ class Plugin(PluginBase): | ||||
|         print(video_data["videos"]) if not keys in ("", None) and "videos" in keys else ... | ||||
|  | ||||
|     def get_video_data(self): | ||||
|         uri            = self._fm_state.selected_files[0] | ||||
|         uri            = self._fm_state.uris[0] | ||||
|         path           = self._fm_state.tab.get_current_directory() | ||||
|         parts          = uri.split("/") | ||||
|         _title         = parts[ len(parts) - 1 ] | ||||
|   | ||||
| @@ -99,7 +99,7 @@ class Plugin(PluginBase): | ||||
|     def delete_files(self, widget = None, eve = None): | ||||
|         self._event_system.emit("get_current_state") | ||||
|         state    = self._fm_state | ||||
|         uris     = state.selected_files | ||||
|         uris     = state.uris | ||||
|         response = None | ||||
|  | ||||
|         state.message_dialog.format_secondary_text(f"Do you really want to delete the {len(uris)} file(s)?") | ||||
| @@ -122,13 +122,13 @@ class Plugin(PluginBase): | ||||
|     def trash_files(self, widget = None, eve = None, verbocity = False): | ||||
|         self._event_system.emit("get_current_state") | ||||
|         state = self._fm_state | ||||
|         for uri in state.selected_files: | ||||
|         for uri in state.uris: | ||||
|             self.trashman.trash(uri, verbocity) | ||||
|  | ||||
|     def restore_trash_files(self, widget = None, eve = None, verbocity = False): | ||||
|         self._event_system.emit("get_current_state") | ||||
|         state = self._fm_state | ||||
|         for uri in state.selected_files: | ||||
|         for uri in state.uris: | ||||
|             self.trashman.restore(filename=uri.split("/")[-1], verbose = verbocity) | ||||
|  | ||||
|     def empty_trash(self, widget = None, eve = None, verbocity = False): | ||||
|   | ||||
| @@ -98,8 +98,8 @@ class Plugin(PluginBase): | ||||
|     def _process_changes(self, state): | ||||
|         self._fm_state = None | ||||
|  | ||||
|         if len(state.selected_files) == 1: | ||||
|             if state.selected_files[0].lower().endswith(state.tab.fvideos): | ||||
|         if len(state.uris) == 1: | ||||
|             if state.uris[0].lower().endswith(state.tab.fvideos): | ||||
|                 self._fm_state = state | ||||
|                 self._set_ui_data() | ||||
|                 response   = self._thumbnailer_dialog.run() | ||||
| @@ -132,7 +132,7 @@ class Plugin(PluginBase): | ||||
|  | ||||
|  | ||||
|     def _set_ui_data(self): | ||||
|         uri            = self._fm_state.selected_files[0] | ||||
|         uri            = self._fm_state.uris[0] | ||||
|         path           = self._fm_state.tab.get_current_directory() | ||||
|         parts          = uri.split("/") | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,7 @@ from gi.repository import GLib | ||||
|  | ||||
| # Application imports | ||||
| from .controller_data import Controller_Data | ||||
| from .fs_actions.file_system_actions import FileSystemActions | ||||
| from .mixins.signals_mixins import SignalsMixins | ||||
|  | ||||
| from .ui.dialogs.about_widget import AboutWidget | ||||
| @@ -57,17 +58,16 @@ class Controller(UIMixin, SignalsMixins, Controller_Data): | ||||
|         ... | ||||
|  | ||||
|     def _setup_signals(self): | ||||
|         ... | ||||
|         FileSystemActions() | ||||
|  | ||||
|     def _subscribe_to_events(self): | ||||
|         event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc) | ||||
|         event_system.subscribe("generate_windows", self.generate_windows) | ||||
|         event_system.subscribe("clear_notebooks", self.clear_notebooks) | ||||
|         event_system.subscribe("get_current_state", self.get_current_state) | ||||
|         event_system.subscribe("go_to_path", self.go_to_path) | ||||
|         event_system.subscribe("do_action_from_menu_controls", self.do_action_from_menu_controls) | ||||
|         # NOTE: Needs to be moved (probably just to file actions class) after reducing mixins usage | ||||
|         event_system.subscribe("open_with_files", self.open_with_files) | ||||
|         event_system.subscribe("generate_windows", self.generate_windows) | ||||
|         event_system.subscribe("set_clipboard_data", self.set_clipboard_data) | ||||
|  | ||||
|     # NOTE: Really we will move these to the UI/(New) Window 'base' controller | ||||
|     #       after we're done cleaning and refactoring to use fewer mixins. | ||||
| @@ -96,36 +96,39 @@ class Controller(UIMixin, SignalsMixins, Controller_Data): | ||||
|         Gtk.main_quit() | ||||
|  | ||||
|  | ||||
|     def do_action_from_menu_controls(self, widget, eve = None): | ||||
|         if not isinstance(widget, str): | ||||
|             action = widget.get_name() | ||||
|     def do_action_from_menu_controls(self, _action=None, eve=None): | ||||
|         if not _action: | ||||
|             return | ||||
|  | ||||
|         if not isinstance(_action, str): | ||||
|             action = _action.get_name() | ||||
|         else: | ||||
|             action = widget | ||||
|             action = _action | ||||
|  | ||||
|         event_system.emit("hide_context_menu") | ||||
|         event_system.emit("hide_new_file_menu") | ||||
|         event_system.emit("hide_rename_file_menu") | ||||
|  | ||||
|         if action == "open": | ||||
|             self.open_files() | ||||
|             event_system.emit("open_files") | ||||
|         if action == "open_with": | ||||
|             event_system.emit("show_appchooser_menu") | ||||
|         if action == "execute": | ||||
|             self.execute_files() | ||||
|             event_system.emit("execute_files") | ||||
|         if action == "execute_in_terminal": | ||||
|             self.execute_files(in_terminal=True) | ||||
|             event_system.emit("execute_files", (True,)) | ||||
|         if action == "rename": | ||||
|             self.rename_files() | ||||
|             event_system.emit("rename_files") | ||||
|         if action == "cut": | ||||
|             self.cut_files() | ||||
|             event_system.emit("cut_files") | ||||
|         if action == "copy": | ||||
|             self.copy_files() | ||||
|             event_system.emit("copy_files") | ||||
|         if action == "copy_name": | ||||
|             self.copy_name() | ||||
|             event_system.emit("copy_name") | ||||
|         if action == "paste": | ||||
|             self.paste_files() | ||||
|             event_system.emit("paste_files") | ||||
|         if action == "create": | ||||
|             self.create_files() | ||||
|             event_system.emit("create_files") | ||||
|         if action in ["save_session", "save_session_as", "load_session"]: | ||||
|             event_system.emit("save_load_session", (action)) | ||||
|  | ||||
| @@ -166,5 +169,5 @@ class Controller(UIMixin, SignalsMixins, Controller_Data): | ||||
|         tab      = self.get_fm_window(wid).get_tab_by_id(tid) | ||||
|         tab.execute([f"{tab.terminal_app}"], start_dir=tab.get_current_directory()) | ||||
|  | ||||
|     def go_to_path(self, path): | ||||
|     def go_to_path(self, path: str): | ||||
|         self.path_entry.set_text(path) | ||||
|   | ||||
| @@ -27,6 +27,7 @@ class State: | ||||
|     tab: type = None | ||||
|     icon_grid: gi.overrides.Gtk.IconView = None | ||||
|     store: gi.overrides.Gtk.ListStore    = None | ||||
|     uris:           []   = None | ||||
|     selected_files: []   = None | ||||
|     to_copy_files:  []   = None | ||||
|     to_cut_files:   []   = None | ||||
| @@ -104,7 +105,9 @@ class Controller_Data: | ||||
|  | ||||
|         selected_files       = state.icon_grid.get_selected_items() | ||||
|         if selected_files: | ||||
|             state.selected_files = self.format_to_uris(state.store, state.wid, state.tid, selected_files, True) | ||||
|             state.uris = self.format_to_uris(state.store, state.wid, state.tid, selected_files, True) | ||||
|  | ||||
|         state.selected_files = self.selected_files | ||||
|  | ||||
|         # if self.to_copy_files: | ||||
|         #     state.to_copy_files  = self.format_to_uris(state.store, state.wid, state.tid, self.to_copy_files, True) | ||||
|   | ||||
| @@ -0,0 +1,3 @@ | ||||
| """ | ||||
|     FS Actions Module | ||||
| """ | ||||
| @@ -0,0 +1,70 @@ | ||||
| # Python imports | ||||
|  | ||||
| # Lib imports | ||||
|  | ||||
| # Application imports | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| class CRUDMixin: | ||||
|     """docstring for CRUDMixin""" | ||||
|  | ||||
|     def move_files(self, files, target): | ||||
|         self.handle_files(files, "move", target) | ||||
|  | ||||
|     def paste_files(self): | ||||
|         state     = event_system.emit_and_await("get_current_state") | ||||
|         target    = f"{state.tab.get_current_directory()}" | ||||
|  | ||||
|         if self._to_copy_files: | ||||
|             self.handle_files(self._to_copy_files, "copy", target) | ||||
|         elif self._to_cut_files: | ||||
|             self.handle_files(self._to_cut_files, "move", target) | ||||
|  | ||||
|     def create_files(self): | ||||
|         fname_field     = self._builder.get_object("new_fname_field") | ||||
|         cancel_creation = event_system.emit_and_await("show_new_file_menu", fname_field) | ||||
|  | ||||
|         if cancel_creation: | ||||
|             event_system.emit("hide_new_file_menu") | ||||
|             return | ||||
|  | ||||
|         file_name = fname_field.get_text().strip() | ||||
|         type      = self._builder.get_object("new_file_toggle_type").get_state() | ||||
|         state     = event_system.emit_and_await("get_current_state") | ||||
|         target    = f"{state.tab.get_current_directory()}" | ||||
|  | ||||
|         if file_name: | ||||
|             path = f"{target}/{file_name}" | ||||
|  | ||||
|             if type == True:     # Create File | ||||
|                 self.handle_files([path], "create_file") | ||||
|             else:                # Create Folder | ||||
|                 self.handle_files([path], "create_dir") | ||||
|  | ||||
|         event_system.emit("hide_new_file_menu") | ||||
|  | ||||
|     def rename_files(self): | ||||
|         rename_label = self._builder.get_object("file_to_rename_label") | ||||
|         rename_input = self._builder.get_object("rename_fname") | ||||
|         state        = event_system.emit_and_await("get_current_state") | ||||
|  | ||||
|         for uri in state.uris: | ||||
|             entry = uri.split("/")[-1] | ||||
|             rename_label.set_label(entry) | ||||
|             rename_input.set_text(entry) | ||||
|  | ||||
|             response = event_system.emit_and_await("show_rename_file_menu", rename_input) | ||||
|             if response == "skip_edit": | ||||
|                 continue | ||||
|             if response == "cancel_edit": | ||||
|                 break | ||||
|  | ||||
|             rname_to = rename_input.get_text().strip() | ||||
|             if rname_to: | ||||
|                 target = f"{state.tab.get_current_directory()}/{rname_to}" | ||||
|                 self.handle_files([uri], "rename", target) | ||||
|  | ||||
|         event_system.emit("hide_rename_file_menu") | ||||
|         event_system.emit_and_await("get_selected_files").clear() | ||||
| @@ -0,0 +1,95 @@ | ||||
| # Python imports | ||||
| import shlex | ||||
|  | ||||
| # Lib imports | ||||
|  | ||||
| # Application imports | ||||
| from .crud_mixin import CRUDMixin | ||||
| from .handler_mixin import HandlerMixin | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| class FileSystemActions(HandlerMixin, CRUDMixin): | ||||
|     """docstring for FileSystemActions""" | ||||
|  | ||||
|     def __init__(self): | ||||
|         super(FileSystemActions, self).__init__() | ||||
|         self._setup_styling() | ||||
|         self._setup_signals() | ||||
|         self._subscribe_to_events() | ||||
|         self._load_widgets() | ||||
|  | ||||
|         self._selected_files = [] | ||||
|         self._to_copy_files  = [] | ||||
|         self._to_cut_files   = [] | ||||
|  | ||||
|         self._builder        = settings.get_builder() | ||||
|  | ||||
|  | ||||
|     def _setup_styling(self): | ||||
|         ... | ||||
|  | ||||
|     def _setup_signals(self): | ||||
|         ... | ||||
|  | ||||
|     def _subscribe_to_events(self): | ||||
|         event_system.subscribe("set_selected_files", self.set_selected_files) | ||||
|         event_system.subscribe("get_selected_files", self.get_selected_files) | ||||
|  | ||||
|         event_system.subscribe("open_files", self.open_files) | ||||
|         event_system.subscribe("open_with_files", self.open_with_files) | ||||
|         event_system.subscribe("execute_files", self.execute_files) | ||||
|  | ||||
|         event_system.subscribe("cut_files", self.cut_files) | ||||
|         event_system.subscribe("copy_files", self.copy_files) | ||||
|         event_system.subscribe("paste_files", self.paste_files) | ||||
|         event_system.subscribe("move_files", self.move_files) | ||||
|         event_system.subscribe("copy_name", self.copy_name) | ||||
|         event_system.subscribe("create_files", self.create_files) | ||||
|         event_system.subscribe("rename_files", self.rename_files) | ||||
|  | ||||
|     def _load_widgets(self): | ||||
|         ... | ||||
|  | ||||
|  | ||||
|     def set_selected_files(self, selected_files: []): | ||||
|         self._selected_files = selected_files | ||||
|  | ||||
|     def get_selected_files(self): | ||||
|         return self._selected_files | ||||
|  | ||||
|  | ||||
|     def cut_files(self): | ||||
|         self._to_copy_files.clear() | ||||
|         state = event_system.emit_and_await("get_current_state") | ||||
|         self._to_cut_files = state.uris | ||||
|  | ||||
|     def copy_files(self): | ||||
|         self._to_cut_files.clear() | ||||
|         state = event_system.emit_and_await("get_current_state") | ||||
|         self._to_copy_files = state.uris | ||||
|  | ||||
|     def copy_name(self): | ||||
|         state = event_system.emit_and_await("get_current_state") | ||||
|         if len(state.uris) == 1: | ||||
|             file_name = state.uris[0].split("/")[-1] | ||||
|             event_system.emit("set_clipboard_data", (file_name,)) | ||||
|  | ||||
|  | ||||
|     def open_files(self): | ||||
|         state = event_system.emit_and_await("get_current_state") | ||||
|         for file in state.uris: | ||||
|             state.tab.open_file_locally(file) | ||||
|  | ||||
|     def open_with_files(self, app_info): | ||||
|         state = event_system.emit_and_await("get_current_state") | ||||
|         state.tab.app_chooser_exec(app_info, state.uris) | ||||
|  | ||||
|     def execute_files(self, in_terminal=False): | ||||
|         state       = event_system.emit_and_await("get_current_state") | ||||
|         current_dir = state.tab.get_current_directory() | ||||
|         command     = None | ||||
|         for path in state.uris: | ||||
|             command = f"{shlex.quote(path)}" if not in_terminal else f"{state.tab.terminal_app} -e {shlex.quote(path)}" | ||||
|             state.tab.execute(shlex.split(command), start_dir=state.tab.get_current_directory()) | ||||
| @@ -0,0 +1,167 @@ | ||||
| # Python imports | ||||
| import os | ||||
|  | ||||
| # Lib imports | ||||
| import gi | ||||
| gi.require_version('Gtk', '3.0') | ||||
| from gi.repository import Gtk | ||||
| from gi.repository import GObject | ||||
| from gi.repository import Gio | ||||
|  | ||||
| # Application imports | ||||
| from ..ui.io_widget import IOWidget | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| class HandlerMixin: | ||||
|     """docstring for HandlerMixin""" | ||||
|  | ||||
|     # NOTE: Gtk recommends using fail flow than pre check 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_files(self, paths, action, _target_path=None): | ||||
|         target          = None | ||||
|         _file           = None | ||||
|         response        = None | ||||
|         overwrite_all   = False | ||||
|         rename_auto_all = False | ||||
|  | ||||
|         for path in paths: | ||||
|             try: | ||||
|                 if "file://" in path: | ||||
|                     path = path.split("file://")[1] | ||||
|  | ||||
|                 file = Gio.File.new_for_path(path) | ||||
|                 if _target_path: | ||||
|                     if file.get_parent().get_path() == _target_path: | ||||
|                         raise Exception("Parent dir of target and file locations are the same! Won't copy or move!") | ||||
|  | ||||
|                     if os.path.isdir(_target_path): | ||||
|                         info    = file.query_info("standard::display-name", 0, cancellable=None) | ||||
|                         _target = f"{_target_path}/{info.get_display_name()}" | ||||
|                         _file   = Gio.File.new_for_path(_target) | ||||
|                     else: | ||||
|                         _file   = Gio.File.new_for_path(_target_path) | ||||
|                 else: | ||||
|                     _file = Gio.File.new_for_path(path) | ||||
|  | ||||
|  | ||||
|                 if _file.query_exists(): | ||||
|                     if not overwrite_all and not rename_auto_all: | ||||
|                         event_system.emit("setup_exists_data", (file, _file)) | ||||
|                         response = event_system.emit_and_await("show_exists_page") | ||||
|  | ||||
|                     if response == "overwrite_all": | ||||
|                         overwrite_all   = True | ||||
|                     if response == "rename_auto_all": | ||||
|                         rename_auto_all = True | ||||
|  | ||||
|                     if response == "rename": | ||||
|                         base_path = _file.get_parent().get_path() | ||||
|                         new_name  = self._builder.get_object("exists_file_field").get_text().strip() | ||||
|                         rfPath    = f"{base_path}/{new_name}" | ||||
|                         _file     = Gio.File.new_for_path(rfPath) | ||||
|  | ||||
|                     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.fm_controller.get_active_wid_and_tid() | ||||
|                             # tab      = self.get_fm_window(wid).get_tab_by_id(tid) | ||||
|                             # tab.delete_file( _file.get_path() ) | ||||
|                             state = event_system.emit_and_await("get_current_state") | ||||
|                             state.tab.delete_file( _file.get_path() ) | ||||
|                         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.fm_controller.get_active_wid_and_tid() | ||||
|                     # tab       = self.get_fm_window(wid).get_tab_by_id(tid) | ||||
|                     state     = event_system.emit_and_await("get_current_state") | ||||
|                     tab       = state.tab | ||||
|                     fPath     = file.get_path() | ||||
|                     tPath     = target.get_path() | ||||
|                     state     = True | ||||
|  | ||||
|                     if action == "copy": | ||||
|                         tab.copy_file(fPath, tPath) | ||||
|                     if action == "move" or action == "rename": | ||||
|                         tab.move_file(fPath, tPath) | ||||
|                 else: | ||||
|                     io_widget = IOWidget(action, file) | ||||
|  | ||||
|                     if action == "copy": | ||||
|                         file.copy_async(destination=target, | ||||
|                                         flags=Gio.FileCopyFlags.BACKUP, | ||||
|                                         io_priority=98, | ||||
|                                         cancellable=io_widget.cancle_eve, | ||||
|                                         progress_callback=io_widget.update_progress, | ||||
|                                         callback=io_widget.finish_callback) | ||||
|  | ||||
|                         self._builder.get_object("io_list").add(io_widget) | ||||
|                     if action == "move" or action == "rename": | ||||
|                         file.move_async(destination=target, | ||||
|                                         flags=Gio.FileCopyFlags.BACKUP, | ||||
|                                         io_priority=98, | ||||
|                                         cancellable=io_widget.cancle_eve, | ||||
|                                         progress_callback=None, | ||||
|                                         # NOTE: progress_callback here causes seg fault when set | ||||
|                                         callback=io_widget.finish_callback) | ||||
|  | ||||
|                         self._builder.get_object("io_list").add(io_widget) | ||||
|  | ||||
|             except GObject.GError as e: | ||||
|                 raise OSError(e) | ||||
|  | ||||
|         self._builder.get_object("exists_file_rename_bttn").set_sensitive(False) | ||||
|  | ||||
|     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) | ||||
|         start     = "-copy" | ||||
|  | ||||
|         if settings.is_debug(): | ||||
|             logger.debug(f"Path:  {full_path}") | ||||
|             logger.debug(f"Base Path:  {base_path}") | ||||
|             logger.debug(f'Name:  {file_name}') | ||||
|             logger.debug(f"Extension:  {extension}") | ||||
|  | ||||
|         i = 2 | ||||
|         while target.query_exists(): | ||||
|             try: | ||||
|                 value     = file_name[(file_name.find(start)+len(start)):] | ||||
|                 int(value) | ||||
|                 file_name = file_name.split(start)[0] | ||||
|             except Exception as e: | ||||
|                 pass | ||||
|  | ||||
|             target = Gio.File.new_for_path(f"{base_path}/{file_name}-copy{i}{extension}") | ||||
|             i += 1 | ||||
|  | ||||
|         return target | ||||
| @@ -1,40 +1,21 @@ | ||||
| # Python imports | ||||
| import os | ||||
| import time | ||||
| import shlex | ||||
|  | ||||
| # Lib imports | ||||
| import gi | ||||
| gi.require_version('Gtk', '3.0') | ||||
| from gi.repository import Gtk | ||||
| from gi.repository import GObject | ||||
| from gi.repository import GLib | ||||
| from gi.repository import Gio | ||||
|  | ||||
|  | ||||
| # Application imports | ||||
| from ...ui.io_widget import IOWidget | ||||
|  | ||||
|  | ||||
|  | ||||
| class FileActionSignalsMixin: | ||||
|     """docstring for FileActionSignalsMixin""" | ||||
|  | ||||
|     def get_dir_size(self, sdir): | ||||
|         """Get the size of a directory. Based on code found online.""" | ||||
|         size = os.path.getsize(sdir) | ||||
|  | ||||
|         for item in os.listdir(sdir): | ||||
|             item = os.path.join(sdir, item) | ||||
|  | ||||
|             if os.path.isfile(item): | ||||
|                 size = size + os.path.getsize(item) | ||||
|             elif os.path.isdir(item): | ||||
|                 size = size + self.get_dir_size(item) | ||||
|  | ||||
|         return size | ||||
|  | ||||
|  | ||||
|     def set_file_watcher(self, tab): | ||||
|         if tab.get_dir_watcher(): | ||||
|             watcher = tab.get_dir_watcher() | ||||
| @@ -120,256 +101,5 @@ class FileActionSignalsMixin: | ||||
|                     icon_grid.select_path(path) | ||||
|  | ||||
|             items = icon_grid.get_selected_items() | ||||
|             if len(items) == 1: | ||||
|                 icon_grid.scroll_to_path(items[-1], True, 0.5, 0.5) | ||||
|  | ||||
|  | ||||
|     def open_files(self): | ||||
|         state = self.get_current_state() | ||||
|         uris  = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) | ||||
|  | ||||
|         for file in uris: | ||||
|             state.tab.open_file_locally(file) | ||||
|  | ||||
|     def open_with_files(self, app_info): | ||||
|         state     = self.get_current_state() | ||||
|         uris      = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files) | ||||
|         state.tab.app_chooser_exec(app_info, uris) | ||||
|  | ||||
|     def execute_files(self, in_terminal=False): | ||||
|         state       = self.get_current_state() | ||||
|         paths       = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) | ||||
|         current_dir = state.tab.get_current_directory() | ||||
|         command     = None | ||||
|         for path in paths: | ||||
|             command = f"{shlex.quote(path)}" if not in_terminal else f"{state.tab.terminal_app} -e {shlex.quote(path)}" | ||||
|             state.tab.execute(shlex.split(command), start_dir=state.tab.get_current_directory()) | ||||
|  | ||||
|     def rename_files(self): | ||||
|         rename_label = self.builder.get_object("file_to_rename_label") | ||||
|         rename_input = self.builder.get_object("rename_fname") | ||||
|         state        = self.get_current_state() | ||||
|         uris         = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) | ||||
|  | ||||
|         for uri in uris: | ||||
|             entry = uri.split("/")[-1] | ||||
|             rename_label.set_label(entry) | ||||
|             rename_input.set_text(entry) | ||||
|  | ||||
|             response = event_system.emit_and_await("show_rename_file_menu", rename_input) | ||||
|             if response == "skip_edit": | ||||
|                 continue | ||||
|             if response == "cancel_edit": | ||||
|                 break | ||||
|  | ||||
|             rname_to = rename_input.get_text().strip() | ||||
|             if rname_to: | ||||
|                 target = f"{state.tab.get_current_directory()}/{rname_to}" | ||||
|                 self.handle_files([uri], "rename", target) | ||||
|  | ||||
|         event_system.emit("hide_rename_file_menu") | ||||
|         self.selected_files.clear() | ||||
|  | ||||
|     def cut_files(self): | ||||
|         self.to_copy_files.clear() | ||||
|         state = self.get_current_state() | ||||
|         uris  = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) | ||||
|         self.to_cut_files = uris | ||||
|  | ||||
|     def copy_name(self): | ||||
|         state = self.get_current_state() | ||||
|         uris  = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) | ||||
|         if len(uris) == 1: | ||||
|             file_name = uris[0].split("/")[-1] | ||||
|             self.set_clipboard_data(file_name) | ||||
|  | ||||
|     def copy_files(self): | ||||
|         self.to_cut_files.clear() | ||||
|         state = self.get_current_state() | ||||
|         uris  = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) | ||||
|         self.to_copy_files = uris | ||||
|  | ||||
|     def paste_files(self): | ||||
|         wid, tid  = self.fm_controller.get_active_wid_and_tid() | ||||
|         tab       = self.get_fm_window(wid).get_tab_by_id(tid) | ||||
|         target    = f"{tab.get_current_directory()}" | ||||
|  | ||||
|         if self.to_copy_files: | ||||
|             self.handle_files(self.to_copy_files, "copy", target) | ||||
|         elif self.to_cut_files: | ||||
|             self.handle_files(self.to_cut_files, "move", target) | ||||
|  | ||||
|     def create_files(self): | ||||
|         fname_field     = self.builder.get_object("new_fname_field") | ||||
|         cancel_creation = event_system.emit_and_await("show_new_file_menu", fname_field) | ||||
|  | ||||
|         if cancel_creation: | ||||
|             event_system.emit("hide_new_file_menu") | ||||
|             return | ||||
|  | ||||
|         file_name   = fname_field.get_text().strip() | ||||
|         type        = self.builder.get_object("new_file_toggle_type").get_state() | ||||
|  | ||||
|         wid, tid    = self.fm_controller.get_active_wid_and_tid() | ||||
|         tab         = self.get_fm_window(wid).get_tab_by_id(tid) | ||||
|         target      = f"{tab.get_current_directory()}" | ||||
|  | ||||
|         if file_name: | ||||
|             path = f"{target}/{file_name}" | ||||
|  | ||||
|             if type == True:     # Create File | ||||
|                 self.handle_files([path], "create_file") | ||||
|             else:                # Create Folder | ||||
|                 self.handle_files([path], "create_dir") | ||||
|  | ||||
|         event_system.emit("hide_new_file_menu") | ||||
|  | ||||
|  | ||||
|     def move_files(self, files, target): | ||||
|         self.handle_files(files, "move", target) | ||||
|  | ||||
|  | ||||
|     # NOTE: Gtk recommends using fail flow than pre check 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_files(self, paths, action, _target_path=None): | ||||
|         target          = None | ||||
|         _file           = None | ||||
|         response        = None | ||||
|         overwrite_all   = False | ||||
|         rename_auto_all = False | ||||
|  | ||||
|         for path in paths: | ||||
|             try: | ||||
|                 if "file://" in path: | ||||
|                     path = path.split("file://")[1] | ||||
|  | ||||
|                 file = Gio.File.new_for_path(path) | ||||
|                 if _target_path: | ||||
|                     if file.get_parent().get_path() == _target_path: | ||||
|                         raise Exception("Parent dir of target and file locations are the same! Won't copy or move!") | ||||
|  | ||||
|                     if os.path.isdir(_target_path): | ||||
|                         info    = file.query_info("standard::display-name", 0, cancellable=None) | ||||
|                         _target = f"{_target_path}/{info.get_display_name()}" | ||||
|                         _file   = Gio.File.new_for_path(_target) | ||||
|                     else: | ||||
|                         _file   = Gio.File.new_for_path(_target_path) | ||||
|                 else: | ||||
|                     _file = Gio.File.new_for_path(path) | ||||
|  | ||||
|  | ||||
|                 if _file.query_exists(): | ||||
|                     if not overwrite_all and not rename_auto_all: | ||||
|                         event_system.emit("setup_exists_data", (file, _file)) | ||||
|                         response = event_system.emit_and_await("show_exists_page") | ||||
|  | ||||
|                     if response == "overwrite_all": | ||||
|                         overwrite_all   = True | ||||
|                     if response == "rename_auto_all": | ||||
|                         rename_auto_all = True | ||||
|  | ||||
|                     if response == "rename": | ||||
|                         base_path = _file.get_parent().get_path() | ||||
|                         new_name  = self.builder.get_object("exists_file_field").get_text().strip() | ||||
|                         rfPath    = f"{base_path}/{new_name}" | ||||
|                         _file     = Gio.File.new_for_path(rfPath) | ||||
|  | ||||
|                     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.fm_controller.get_active_wid_and_tid() | ||||
|                             tab      = self.get_fm_window(wid).get_tab_by_id(tid) | ||||
|                             tab.delete_file( _file.get_path() ) | ||||
|                         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.fm_controller.get_active_wid_and_tid() | ||||
|                     tab       = self.get_fm_window(wid).get_tab_by_id(tid) | ||||
|                     fPath     = file.get_path() | ||||
|                     tPath     = target.get_path() | ||||
|                     state     = True | ||||
|  | ||||
|                     if action == "copy": | ||||
|                         tab.copy_file(fPath, tPath) | ||||
|                     if action == "move" or action == "rename": | ||||
|                         tab.move_file(fPath, tPath) | ||||
|                 else: | ||||
|                     io_widget = IOWidget(action, file) | ||||
|  | ||||
|                     if action == "copy": | ||||
|                         file.copy_async(destination=target, | ||||
|                                         flags=Gio.FileCopyFlags.BACKUP, | ||||
|                                         io_priority=98, | ||||
|                                         cancellable=io_widget.cancle_eve, | ||||
|                                         progress_callback=io_widget.update_progress, | ||||
|                                         callback=io_widget.finish_callback) | ||||
|  | ||||
|                         self.builder.get_object("io_list").add(io_widget) | ||||
|                     if action == "move" or action == "rename": | ||||
|                         file.move_async(destination=target, | ||||
|                                         flags=Gio.FileCopyFlags.BACKUP, | ||||
|                                         io_priority=98, | ||||
|                                         cancellable=io_widget.cancle_eve, | ||||
|                                         progress_callback=None, | ||||
|                                         # NOTE: progress_callback here causes seg fault when set | ||||
|                                         callback=io_widget.finish_callback) | ||||
|  | ||||
|                         self.builder.get_object("io_list").add(io_widget) | ||||
|  | ||||
|             except GObject.GError as e: | ||||
|                 raise OSError(e) | ||||
|  | ||||
|         self.builder.get_object("exists_file_rename_bttn").set_sensitive(False) | ||||
|  | ||||
|     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) | ||||
|         start     = "-copy" | ||||
|  | ||||
|         if settings.is_debug(): | ||||
|             logger.debug(f"Path:  {full_path}") | ||||
|             logger.debug(f"Base Path:  {base_path}") | ||||
|             logger.debug(f'Name:  {file_name}') | ||||
|             logger.debug(f"Extension:  {extension}") | ||||
|  | ||||
|         i = 2 | ||||
|         while target.query_exists(): | ||||
|             try: | ||||
|                 value     = file_name[(file_name.find(start)+len(start)):] | ||||
|                 int(value) | ||||
|                 file_name = file_name.split(start)[0] | ||||
|             except Exception as e: | ||||
|                 pass | ||||
|  | ||||
|             target = Gio.File.new_for_path(f"{base_path}/{file_name}-copy{i}{extension}") | ||||
|             i += 1 | ||||
|  | ||||
|         return target | ||||
|             if len(items) > 0: | ||||
|                 icon_grid.scroll_to_path(items[0], False, 0.5, 0.5) | ||||
|   | ||||
| @@ -50,7 +50,11 @@ class KeyboardSignalsMixin: | ||||
|         if mapping: | ||||
|             try: | ||||
|                 # See if in filemanager scope | ||||
|                 try: | ||||
|                     getattr(self, mapping)() | ||||
|                 except Exception as e: | ||||
|                     event_system.emit(mapping) | ||||
|  | ||||
|                 return True | ||||
|             except Exception: | ||||
|                 # Must be plugins scope or we forgot to add method to file manager scope | ||||
|   | ||||
| @@ -165,12 +165,14 @@ class WindowMixin(TabMixin): | ||||
|  | ||||
|         if size == 1: | ||||
|             # NOTE: If already in selection, likely dnd else not so wont readd | ||||
|             if items[0] in self.selected_files: | ||||
|             # if items[0] in self.selected_files: | ||||
|             if items[0] in event_system.emit_and_await("get_selected_files"): | ||||
|                 self.dnd_left_primed += 1 | ||||
|                 # NOTE: If in selection but trying to just select an already selected item. | ||||
|                 if self.dnd_left_primed > 1: | ||||
|                     self.dnd_left_primed = 0 | ||||
|                     self.selected_files.clear() | ||||
|                     event_system.emit_and_await("get_selected_files").clear() | ||||
|                     # self.selected_files.clear() | ||||
|  | ||||
|                 # NOTE: Likely trying dnd, just readd to selection the former set. | ||||
|                 #       Prevents losing highlighting of grid selected. | ||||
| @@ -178,10 +180,11 @@ class WindowMixin(TabMixin): | ||||
|                     icons_grid.select_path(path) | ||||
|  | ||||
|         if size > 0: | ||||
|             self.selected_files = icons_grid.get_selected_items() | ||||
|             # self.selected_files = icons_grid.get_selected_items() | ||||
|             event_system.emit("set_selected_files", (icons_grid.get_selected_items(),)) | ||||
|         else: | ||||
|             self.dnd_left_primed = 0 | ||||
|             self.selected_files.clear() | ||||
|             event_system.emit_and_await("get_selected_files").clear() | ||||
|  | ||||
|     def grid_icon_single_click(self, icons_grid, eve): | ||||
|         try: | ||||
| @@ -228,7 +231,7 @@ class WindowMixin(TabMixin): | ||||
|                 state.tab.set_path(file) | ||||
|                 self.update_tab(tab_label, state.tab, state.store, state.wid, state.tid) | ||||
|             else: | ||||
|                 self.open_files() | ||||
|                 event_system.emit("open_files") | ||||
|         except WindowException as e: | ||||
|             traceback.print_exc() | ||||
|             self.display_message(settings.get_error_color(), f"{repr(e)}") | ||||
| @@ -282,7 +285,7 @@ class WindowMixin(TabMixin): | ||||
|  | ||||
|             from_uri = '/'.join(uris[0].replace("file://", "").split("/")[:-1]) | ||||
|             if from_uri != dest: | ||||
|                 self.move_files(uris, dest) | ||||
|                 event_system.emit("move_files", (uris, dest)) | ||||
|  | ||||
|  | ||||
|     def create_new_tab_notebook(self, widget=None, wid=None, path=None): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user