Finally resolved UI thread overloads

This commit is contained in:
itdominator 2022-10-06 20:48:44 -05:00
parent e929e9b742
commit 206f67f2f0
5 changed files with 132 additions and 69 deletions

View File

@ -12,12 +12,14 @@ from ..widgets.file_preview_widget import FilePreviewWidget
def threaded(fn): def threaded(fn):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
return wrapper return wrapper
# NOTE: Threads WILL die with parent's destruction. # NOTE: Threads WILL die with parent's destruction.
def daemon_threaded(fn): def daemon_threaded(fn):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper return wrapper
@ -25,6 +27,7 @@ class FileSearchMixin:
def _run_find_file_query(self, widget=None, eve=None): def _run_find_file_query(self, widget=None, eve=None):
self._handle_find_file_query(query=widget) self._handle_find_file_query(query=widget)
# TODO: Merge this logic with nearly the exact same thing in grep_search_mixin
@daemon_threaded @daemon_threaded
def _handle_find_file_query(self, widget=None, eve=None, query=None): def _handle_find_file_query(self, widget=None, eve=None, query=None):
# NOTE: Freeze IPC consumption # NOTE: Freeze IPC consumption
@ -33,19 +36,15 @@ class FileSearchMixin:
# NOTE: Kill the former process # NOTE: Kill the former process
if self._list_proc: if self._list_proc:
if self._list_proc.poll(): if self._list_proc.poll() == None:
self._list_proc.send_signal(signal.SIGKILL) self._list_proc.terminate()
while self._list_proc.poll(): while self._list_proc.poll() == None:
pass ...
self._list_proc = None self._list_proc = None
else:
self._list_proc = None
# NOTE: Clear children from ui and make sure ui thread redraws # NOTE: Clear children from ui and make sure ui thread redraws
GLib.idle_add(self.clear_children, self._file_list) GLib.idle_add(self.reset_file_list_box)
while len(self._file_list.get_children()) > 0:
time.sleep(0.2)
# NOTE: If query create new process and do all new loop. # NOTE: If query create new process and do all new loop.
self.pause_fifo_update = False self.pause_fifo_update = False
@ -62,9 +61,6 @@ class FileSearchMixin:
self._list_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) self._list_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None)
def _load_file_ui(self, data): def _load_file_ui(self, data):
if self.pause_fifo_update:
return
if not data in ("", None): if not data in ("", None):
jdata = json.loads( data ) jdata = json.loads( data )
target = jdata[0] target = jdata[0]

View File

@ -12,12 +12,14 @@ from ..widgets.grep_preview_widget import GrepPreviewWidget
def threaded(fn): def threaded(fn):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
return wrapper return wrapper
# NOTE: Threads WILL die with parent's destruction. # NOTE: Threads WILL die with parent's destruction.
def daemon_threaded(fn): def daemon_threaded(fn):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper return wrapper
@ -25,6 +27,7 @@ class GrepSearchMixin:
def _run_grep_query(self, widget=None, eve=None): def _run_grep_query(self, widget=None, eve=None):
self._handle_grep_query(query=widget) self._handle_grep_query(query=widget)
# TODO: Merge this logic with nearly the exact same thing in file_search_mixin
@daemon_threaded @daemon_threaded
def _handle_grep_query(self, widget=None, eve=None, query=None): def _handle_grep_query(self, widget=None, eve=None, query=None):
# NOTE: Freeze IPC consumption # NOTE: Freeze IPC consumption
@ -33,19 +36,15 @@ class GrepSearchMixin:
# NOTE: Kill the former process # NOTE: Kill the former process
if self._grep_proc: if self._grep_proc:
if self._grep_proc.poll(): if self._grep_proc.poll() == None:
self._grep_proc.send_signal(signal.SIGKILL) self._grep_proc.terminate()
while self._grep_proc.poll(): while self._grep_proc.poll() == None:
pass ...
self._grep_proc = None self._grep_proc = None
else:
self._grep_proc = None
# NOTE: Clear children from ui and make sure ui thread redraws # NOTE: Clear children from ui and make sure ui thread redraws
GLib.idle_add(self.clear_children, self._grep_list) GLib.idle_add(self.reset_grep_box)
while len(self._grep_list.get_children()) > 0:
time.sleep(0.2)
# NOTE: If query create new process and do all new loop. # NOTE: If query create new process and do all new loop.
self.pause_fifo_update = False self.pause_fifo_update = False
@ -62,9 +61,6 @@ class GrepSearchMixin:
self._grep_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) self._grep_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None)
def _load_grep_ui(self, data): def _load_grep_ui(self, data):
if self.pause_fifo_update:
return
if not data in ("", None): if not data in ("", None):
jdata = json.loads( data ) jdata = json.loads( data )
jkeys = jdata.keys() jkeys = jdata.keys()

View File

@ -40,6 +40,8 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase):
self._search_dialog = None self._search_dialog = None
self._active_path = None self._active_path = None
self.file_list_parent = None
self.grep_list_parent = None
self._file_list = None self._file_list = None
self._grep_list = None self._grep_list = None
self._grep_proc = None self._grep_proc = None
@ -72,10 +74,11 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase):
self._builder.connect_signals(handlers) self._builder.connect_signals(handlers)
self._search_dialog = self._builder.get_object("search_dialog") self._search_dialog = self._builder.get_object("search_dialog")
self._grep_list = self._builder.get_object("grep_list")
self._file_list = self._builder.get_object("file_list")
self.fsearch = self._builder.get_object("fsearch") self.fsearch = self._builder.get_object("fsearch")
self.grep_list_parent = self._builder.get_object("grep_list_parent")
self.file_list_parent = self._builder.get_object("file_list_parent")
self._event_system.subscribe("update-file-ui", self._load_file_ui) self._event_system.subscribe("update-file-ui", self._load_file_ui)
self._event_system.subscribe("update-grep-ui", self._load_grep_ui) self._event_system.subscribe("update-grep-ui", self._load_grep_ui)
@ -92,9 +95,29 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase):
response = self._search_dialog.run() response = self._search_dialog.run()
self._search_dialog.hide() self._search_dialog.hide()
# TODO: Merge the below methods into some unified logic
def reset_grep_box(self) -> None:
try:
child = self.grep_list_parent.get_children()[0]
self._grep_list = None
self.grep_list_parent.remove(child)
except Exception:
...
def clear_children(self, widget: type) -> None: self._grep_list = Gtk.Box()
''' Clear children of a gtk widget. ''' self._grep_list.set_orientation(Gtk.Orientation.VERTICAL)
for child in widget.get_children(): self.grep_list_parent.add(self._grep_list)
widget.remove(child) self.grep_list_parent.show_all()
time.sleep(0.01)
def reset_file_list_box(self) -> None:
try:
child = self.file_list_parent.get_children()[0]
self._file_list = None
self.file_list_parent.remove(child)
except Exception:
...
self._file_list = Gtk.Box()
self._file_list.set_orientation(Gtk.Orientation.VERTICAL)
self.file_list_parent.add(self._file_list)
self.file_list_parent.show_all()

View File

@ -74,16 +74,41 @@
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkSearchEntry" id="fsearch"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Query...</property> <child>
<property name="primary-icon-name">edit-find-symbolic</property> <object class="GtkSearchEntry" id="fsearch">
<property name="primary-icon-activatable">False</property> <property name="visible">True</property>
<property name="primary-icon-sensitive">False</property> <property name="can-focus">True</property>
<property name="placeholder-text" translatable="yes">Search for file...</property> <property name="tooltip-text" translatable="yes">Query...</property>
<signal name="search-changed" handler="_run_find_file_query" swapped="no"/> <property name="primary-icon-name">edit-find-symbolic</property>
<signal name="stop-search" handler="_handle_find_file_query" swapped="no"/> <property name="primary-icon-activatable">False</property>
<property name="primary-icon-sensitive">False</property>
<property name="placeholder-text" translatable="yes">Search for file...</property>
<signal name="search-changed" handler="_run_find_file_query" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-stop</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
<signal name="released" handler="_handle_find_file_query" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -103,7 +128,7 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<child> <child>
<object class="GtkBox" id="file_list"> <object class="GtkBox" id="file_list_parent">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
@ -141,16 +166,41 @@
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkSearchEntry"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Query...</property> <child>
<property name="primary-icon-name">edit-find-symbolic</property> <object class="GtkSearchEntry">
<property name="primary-icon-activatable">False</property> <property name="visible">True</property>
<property name="primary-icon-sensitive">False</property> <property name="can-focus">True</property>
<property name="placeholder-text" translatable="yes">Query string in file...</property> <property name="tooltip-text" translatable="yes">Query...</property>
<signal name="search-changed" handler="_run_grep_query" swapped="no"/> <property name="primary-icon-name">edit-find-symbolic</property>
<signal name="stop-search" handler="_handle_grep_query" swapped="no"/> <property name="primary-icon-activatable">False</property>
<property name="primary-icon-sensitive">False</property>
<property name="placeholder-text" translatable="yes">Query string in file...</property>
<signal name="search-changed" handler="_run_grep_query" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-stop</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
<signal name="released" handler="_handle_grep_query" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -170,12 +220,10 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<child> <child>
<object class="GtkBox" id="grep_list"> <object class="GtkBox" id="grep_list_parent">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">5</property>
<property name="baseline-position">top</property>
<child> <child>
<placeholder/> <placeholder/>
</child> </child>

View File

@ -2,7 +2,7 @@
# Python imports # Python imports
import os, traceback, argparse, threading, json, base64, time import os, traceback, argparse, threading, json, base64, time, pickle
from setproctitle import setproctitle from setproctitle import setproctitle
from multiprocessing.connection import Client from multiprocessing.connection import Client
@ -37,19 +37,18 @@ def send_ipc_message(message) -> None:
conn.send(message) conn.send(message)
conn.close() conn.close()
time.sleep(0.05) # NOTE: Kinda important as this prevents overloading the UI thread
time.sleep(0.04)
def file_search(path, query): def file_search(path, query):
try: try:
for file in os.listdir(path): for _path, _dir, _files in os.walk(path, topdown = True):
target = os.path.join(path, file) for file in _files:
if os.path.isdir(target): if query in file.lower():
file_search(target, query) target = os.path.join(_path, file)
else: data = f"SEARCH|{json.dumps([target, file])}"
if query in file.lower(): send_ipc_message(data)
data = f"SEARCH|{json.dumps([target, file])}"
send_ipc_message(data)
except Exception as e: except Exception as e:
print("Couldn't traverse to path. Might be permissions related...") print("Couldn't traverse to path. Might be permissions related...")
traceback.print_exc() traceback.print_exc()
@ -59,8 +58,9 @@ def _search_for_string(file, query):
grep_result_set = {} grep_result_set = {}
padding = 15 padding = 15
with open(file, 'r') as fp: with open(file, 'r') as fp:
# NOTE: I know there's an issue if there's a very large file with content all on one line will lower and dupe it. # NOTE: I know there's an issue if there's a very large file with content
# And, yes, it will only return one instance from the file. # all on one line will lower and dupe it. And, yes, it will only
# return one instance from the file.
for i, raw in enumerate(fp): for i, raw in enumerate(fp):
line = None line = None
llower = raw.lower() llower = raw.lower()
@ -87,11 +87,11 @@ def _search_for_string(file, query):
data = f"GREP|{json.dumps(grep_result_set)}" data = f"GREP|{json.dumps(grep_result_set)}"
send_ipc_message(data) send_ipc_message(data)
@daemon_threaded @daemon_threaded
def _search_for_string_threaded(file, query): def _search_for_string_threaded(file, query):
_search_for_string(file, query) _search_for_string(file, query)
def grep_search(path, query): def grep_search(path, query):
try: try:
for file in os.listdir(path): for file in os.listdir(path):
@ -101,7 +101,7 @@ def grep_search(path, query):
else: else:
if target.lower().endswith(filter): if target.lower().endswith(filter):
size = os.path.getsize(target) size = os.path.getsize(target)
if size < 5000: if not size > 5000:
_search_for_string(target, query) _search_for_string(target, query)
else: else:
_search_for_string_threaded(target, query) _search_for_string_threaded(target, query)