diff --git a/plugins/searcher/mixins/__init__.py b/plugins/searcher/mixins/__init__.py new file mode 100644 index 0000000..0c12f42 --- /dev/null +++ b/plugins/searcher/mixins/__init__.py @@ -0,0 +1,3 @@ +""" + Mixins Module +""" diff --git a/plugins/searcher/mixins/file_search_mixin.py b/plugins/searcher/mixins/file_search_mixin.py new file mode 100644 index 0000000..4095d65 --- /dev/null +++ b/plugins/searcher/mixins/file_search_mixin.py @@ -0,0 +1,71 @@ +# Python imports +import threading, subprocess, signal, time, json, shlex + +# Lib imports +from gi.repository import GLib + +# Application imports +from ..widgets.file_preview_widget import FilePreviewWidget + + +# NOTE: Threads WILL NOT die with parent's destruction. +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Threads WILL die with parent's destruction. +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + +class FileSearchMixin: + def _run_find_file_query(self, widget=None, eve=None): + self._handle_find_file_query(query=widget) + + @daemon_threaded + def _handle_find_file_query(self, widget=None, eve=None, query=None): + # NOTE: Freeze IPC consumption + self.pause_fifo_update = True + + # NOTE: Kill the former process + if self._list_proc: + if self._list_proc.poll(): + self._list_proc.send_signal(signal.SIGKILL) + while self._list_proc.poll(): + pass + + self._list_proc = None + else: + self._list_proc = None + + GLib.idle_add(self.clear_children, self._file_list) + while len(self._file_list.get_children()) > 0: + ... + + # NOTE: Make sure ui thread redraws + time.sleep(0.5) + self.pause_fifo_update = False + + # NOTE: If query create new process and do all new loop. + if query: + GLib.idle_add(self._exec_find_file_query, query) + + def _exec_find_file_query(self, widget=None, eve=None): + query = widget.get_text() + + if not query in ("", None): + target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) + command = ["python", f"{self.path}/utils/search.py", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] + self._list_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) + + def _load_file_ui(self, data): + if not data in ("", None) and not self.pause_fifo_update: + jdata = json.loads( data ) + target = jdata[0] + file = jdata[1] + + widget = FilePreviewWidget(target, file) + self._file_list.add(widget) diff --git a/plugins/searcher/mixins/grep_search_mixin.py b/plugins/searcher/mixins/grep_search_mixin.py new file mode 100644 index 0000000..860a346 --- /dev/null +++ b/plugins/searcher/mixins/grep_search_mixin.py @@ -0,0 +1,73 @@ +# Python imports +import threading, subprocess, signal, time, json, shlex + +# Lib imports +from gi.repository import GLib + +# Application imports +from ..widgets.grep_preview_widget import GrepPreviewWidget + + +# NOTE: Threads WILL NOT die with parent's destruction. +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Threads WILL die with parent's destruction. +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + +class GrepSearchMixin: + def _run_grep_query(self, widget=None, eve=None): + self._handle_grep_query(query=widget) + + @daemon_threaded + def _handle_grep_query(self, widget=None, eve=None, query=None): + # NOTE: Freeze IPC consumption + self.pause_fifo_update = True + + # NOTE: Kill the former process + if self._grep_proc: + if self._grep_proc.poll(): + self._grep_proc.send_signal(signal.SIGKILL) + while self._grep_proc.poll(): + pass + + self._grep_proc = None + else: + self._grep_proc = None + + # NOTE: Clear children from ui + GLib.idle_add(self.clear_children, self._grep_list) + while len(self._grep_list.get_children()) > 0: + ... + + # NOTE: Make sure ui thread redraws + time.sleep(0.5) + self.pause_fifo_update = False + + # NOTE: If query create new process and do all new loop. + if query: + GLib.idle_add(self._exec_grep_query, query) + + def _exec_grep_query(self, widget=None, eve=None): + query = widget.get_text() + if not query in ("", None): + target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) + command = ["python", f"{self.path}/utils/search.py", "-t", "grep_search", "-d", f"{target_dir}", "-q", f"{query}"] + self._grep_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) + + def _load_grep_ui(self, data): + if not data in ("", None) and not self.pause_fifo_update: + jdata = json.loads( data ) + jkeys = jdata.keys() + for key in jkeys: + sub_keys = jdata[key].keys() + grep_result = jdata[key] + + widget = GrepPreviewWidget(key, sub_keys, grep_result) + self._grep_list.add(widget) diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index cedbfdf..61eaeda 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -1,5 +1,5 @@ # Python imports -import os, threading, subprocess, inspect, time, json, base64, shlex, select, signal +import os, threading, inspect # Lib imports import gi @@ -8,7 +8,9 @@ from gi.repository import Gtk, GLib # Application imports from plugins.plugin_base import PluginBase -from .ipc_server import IPCServer +from .mixins.file_search_mixin import FileSearchMixin +from .mixins.grep_search_mixin import GrepSearchMixin +from .utils.ipc_server import IPCServer @@ -27,54 +29,7 @@ def daemon_threaded(fn): -class FilePreviewWidget(Gtk.LinkButton): - def __init__(self, path, file): - super(FilePreviewWidget, self).__init__() - self.set_label(file) - self.set_uri(f"file://{path}") - self.show_all() - - -class GrepPreviewWidget(Gtk.Box): - def __init__(self, _path, sub_keys, data): - super(GrepPreviewWidget, self).__init__() - self.set_orientation(Gtk.Orientation.VERTICAL) - self.line_color = "#e0cc64" - - path = base64.urlsafe_b64decode(_path.encode('utf-8')).decode('utf-8') - _label = '/'.join( path.split("/")[-3:] ) - title = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=_label) - - self.add(title) - for key in sub_keys: - line_num = key - text = base64.urlsafe_b64decode(data[key].encode('utf-8')).decode('utf-8') - - - box = Gtk.Box() - number_label = Gtk.Label() - text_view = Gtk.Label(label=text[:-1]) - label_text = f"{line_num}" - - number_label.set_markup(label_text) - number_label.set_margin_left(15) - number_label.set_margin_right(5) - number_label.set_margin_top(5) - number_label.set_margin_bottom(5) - text_view.set_margin_top(5) - text_view.set_margin_bottom(5) - text_view.set_line_wrap(True) - - box.add(number_label) - box.add(text_view) - self.add(box) - - self.show_all() - - -pause_fifo_update = False - -class Plugin(IPCServer, PluginBase): +class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): def __init__(self): super().__init__() @@ -89,11 +44,13 @@ class Plugin(IPCServer, PluginBase): self._grep_list = None self._grep_proc = None self._list_proc = None + self.pause_fifo_update = False + self.update_list_ui_buffer = () def get_ui_element(self): button = Gtk.Button(label=self.name) - button.connect("button-release-event", self._show_grep_list_page) + button.connect("button-release-event", self._show_page) return button def run(self): @@ -123,7 +80,7 @@ class Plugin(IPCServer, PluginBase): self.create_ipc_listener() - def _show_grep_list_page(self, widget=None, eve=None): + def _show_page(self, widget=None, eve=None): self._event_system.emit("get_current_state") state = self._fm_state @@ -134,74 +91,7 @@ class Plugin(IPCServer, PluginBase): self._search_dialog.hide() - def _run_find_file_query(self, widget=None, eve=None): - self._stop_find_file_query() - - query = widget.get_text() - if not query in ("", None): - target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) - command = ["python", f"{self.path}/search.py", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] - process = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) - - def _stop_find_file_query(self, widget=None, eve=None): - pause_fifo_update = True - - if self._list_proc: - if self._list_proc.poll(): - self._list_proc.send_signal(signal.SIGKILL) - while self._list_proc.poll(): - pass - - self._list_proc = None - else: - self._list_proc = None - - self.clear_children(self._file_list) - pause_fifo_update = False - - - def _run_grep_query(self, widget=None, eve=None): - self._stop_grep_query() - - query = widget.get_text() - if not query in ("", None): - target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) - command = ["python", f"{self.path}/search.py", "-t", "grep_search", "-d", f"{target_dir}", "-q", f"{query}"] - process = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) - - def _stop_grep_query(self, widget=None, eve=None): - pause_fifo_update = True - - if self._grep_proc: - if self._grep_proc.poll(): - self._grep_proc.send_signal(signal.SIGKILL) - while self._grep_proc.poll(): - pass - - self._grep_proc = None - else: - self._grep_proc = None - - self.clear_children(self._grep_list) - pause_fifo_update = False - - - def _load_file_ui(self, data): - if not data in ("", None) and not pause_fifo_update: - jdata = json.loads( data ) - target = jdata[0] - file = jdata[1] - - widget = FilePreviewWidget(target, file) - self._file_list.add(widget) - - def _load_grep_ui(self, data): - if not data in ("", None) and not pause_fifo_update: - jdata = json.loads( data ) - jkeys = jdata.keys() - for key in jkeys: - sub_keys = jdata[key].keys() - grep_result = jdata[key] - - widget = GrepPreviewWidget(key, sub_keys, grep_result) - self._grep_list.add(widget) + def clear_children(self, widget: type) -> None: + ''' Clear children of a gtk widget. ''' + for child in widget.get_children(): + widget.remove(child) diff --git a/plugins/searcher/search_dialog.glade b/plugins/searcher/search_dialog.glade index 9fbca4b..aaa3b09 100644 --- a/plugins/searcher/search_dialog.glade +++ b/plugins/searcher/search_dialog.glade @@ -83,7 +83,7 @@ False Search for file... - + False @@ -150,7 +150,7 @@ False Query string in file... - + False diff --git a/plugins/searcher/ipc_server.py b/plugins/searcher/utils/ipc_server.py similarity index 74% rename from plugins/searcher/ipc_server.py rename to plugins/searcher/utils/ipc_server.py index c654842..937a474 100644 --- a/plugins/searcher/ipc_server.py +++ b/plugins/searcher/utils/ipc_server.py @@ -57,32 +57,35 @@ class IPCServer: def handle_message(self, conn, start_time) -> None: while True: msg = conn.recv() - data = msg - if "SEARCH|" in msg: - file = msg.split("SEARCH|")[1].strip() - if file: - GLib.idle_add(self._load_file_ui, file) + if not self.pause_fifo_update: + if "SEARCH|" in msg: + file = msg.split("SEARCH|")[1].strip() + if file: + GLib.idle_add(self._load_file_ui, file) - conn.close() - break + conn.close() + break - if "GREP|" in msg: - data = msg.split("GREP|")[1].strip() - if data: - GLib.idle_add(self._load_grep_ui, data) + if "GREP|" in msg: + data = msg.split("GREP|")[1].strip() + if data: + GLib.idle_add(self._load_grep_ui, data) - conn.close() - break + conn.close() + break - if msg in ['close connection', 'close server']: - conn.close() - break + if msg in ['close connection', 'close server']: + conn.close() + break - # NOTE: Not perfect but insures we don't lock up the connection for too long. - end_time = time.perf_counter() - if (end_time - start_time) > self._ipc_timeout: + # NOTE: Not perfect but insures we don't lock up the connection for too long. + end_time = time.perf_counter() + if (end_time - start_time) > self._ipc_timeout: + conn.close() + break + else: conn.close() break diff --git a/plugins/searcher/search.py b/plugins/searcher/utils/search.py similarity index 100% rename from plugins/searcher/search.py rename to plugins/searcher/utils/search.py diff --git a/plugins/searcher/widgets/__init__.py b/plugins/searcher/widgets/__init__.py new file mode 100644 index 0000000..72b072b --- /dev/null +++ b/plugins/searcher/widgets/__init__.py @@ -0,0 +1,3 @@ +""" + Widgets Module +""" diff --git a/plugins/searcher/widgets/file_preview_widget.py b/plugins/searcher/widgets/file_preview_widget.py new file mode 100644 index 0000000..227e37a --- /dev/null +++ b/plugins/searcher/widgets/file_preview_widget.py @@ -0,0 +1,16 @@ +# Python imports + +# Gtk imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports + + +class FilePreviewWidget(Gtk.LinkButton): + def __init__(self, path, file): + super(FilePreviewWidget, self).__init__() + self.set_label(file) + self.set_uri(f"file://{path}") + self.show_all() diff --git a/plugins/searcher/widgets/grep_preview_widget.py b/plugins/searcher/widgets/grep_preview_widget.py new file mode 100644 index 0000000..85aa2f8 --- /dev/null +++ b/plugins/searcher/widgets/grep_preview_widget.py @@ -0,0 +1,46 @@ +# Python imports +import base64 + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports + + +class GrepPreviewWidget(Gtk.Box): + def __init__(self, _path, sub_keys, data): + super(GrepPreviewWidget, self).__init__() + self.set_orientation(Gtk.Orientation.VERTICAL) + self.line_color = "#e0cc64" + + path = base64.urlsafe_b64decode(_path.encode('utf-8')).decode('utf-8') + _label = '/'.join( path.split("/")[-3:] ) + title = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=_label) + + self.add(title) + for key in sub_keys: + line_num = key + text = base64.urlsafe_b64decode(data[key].encode('utf-8')).decode('utf-8') + + + box = Gtk.Box() + number_label = Gtk.Label() + text_view = Gtk.Label(label=text[:-1]) + label_text = f"{line_num}" + + number_label.set_markup(label_text) + number_label.set_margin_left(15) + number_label.set_margin_right(5) + number_label.set_margin_top(5) + number_label.set_margin_bottom(5) + text_view.set_margin_top(5) + text_view.set_margin_bottom(5) + text_view.set_line_wrap(True) + + box.add(number_label) + box.add(text_view) + self.add(box) + + self.show_all()