diff --git a/filterwidget.py b/filterwidget.py index 8c1df33..89b3b08 100644 --- a/filterwidget.py +++ b/filterwidget.py @@ -1,11 +1,12 @@ import os import re import tempfile +import threading import time -from typing import Optional +from typing import Optional, Callable from PyQt6.QtCore import QRunnable, QThreadPool -from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QCheckBox +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QCheckBox, QPushButton from bigtext import BigText from logFileModel import LogFileModel @@ -19,36 +20,46 @@ class FilterTask(QRunnable): self, source_model: LogFileModel, filter_model: LogFileModel, - regex: re.Pattern + regex: re.Pattern, + lock: threading.RLock, + on_before: Callable[[], None], + on_finish: Callable[[], None] ): super(FilterTask, self).__init__() self.source_model = source_model self.filter_model = filter_model self.regex = regex - self.future = asyncio.Future() + self.on_before = on_before + self.on_finish = on_finish + self.lock = lock def run(self): # print("writing to tmp file", self.filter_model.get_file()) - start = time.time() - with open(self.source_model.get_file(), "rb") as source: - with open(self.filter_model.get_file(), "w+b") as target: - while l := source.readline(): - line = l.decode("utf8", errors="ignore") - if self.regex.findall(line): - target.write(line.encode("utf8")) + # the lock ensures that we only start a new search when the previous search already ended + with self.lock: + self.on_before() + try: + with open(self.source_model.get_file(), "rb") as source: + with open(self.filter_model.get_file(), "w+b") as target: + while l := source.readline(): + line = l.decode("utf8", errors="ignore") - if self.aborted or self.future.cancelled(): - print("aborted") - break - #print("filtering for %s took %s" % (self.regex, time.time() - start)) - self.future.done() + if self.regex.findall(line): + target.write(line.encode("utf8")) + + if self.aborted: + #print("aborted ", time.time()) + break + finally: + self.on_finish() class FilterWidget(QWidget): future = None filter_model: LogFileModel filter_task: Optional[FilterTask] = None + _lock = threading.RLock() def __init__(self, source_model: LogFileModel): super(FilterWidget, self).__init__() @@ -60,6 +71,10 @@ class FilterWidget(QWidget): self.query_field = QLineEdit() self.query_field.textChanged.connect(self.filter_changed) + self.btn_cancel_search = QPushButton(self.tr("Cancel")) + self.btn_cancel_search.setVisible(False) + self.btn_cancel_search.pressed.connect(self._cancel_search) + self.ignore_case = QCheckBox(self.tr("ignore case")) self.ignore_case.setChecked(True) self.ignore_case.stateChanged.connect(self.filter_changed) @@ -72,6 +87,7 @@ class FilterWidget(QWidget): filter_bar.layout = QHBoxLayout(filter_bar) filter_bar.layout.setContentsMargins(0, 0, 0, 0) filter_bar.layout.addWidget(self.query_field) + filter_bar.layout.addWidget(self.btn_cancel_search) filter_bar.layout.addWidget(self.ignore_case) filter_bar.layout.addWidget(self.is_regex) @@ -83,9 +99,14 @@ class FilterWidget(QWidget): self.layout.addWidget(self.hits_view) def destruct(self): - print("cleanup: ", self.tmpfilename) + #print("cleanup: ", self.tmpfilename) os.remove(self.tmpfilename) + def _cancel_search(self): + if self.filter_task: + #print("cancel started ", time.time()) + self.filter_task.aborted = True + def filter_changed(self): query = self.query_field.text() ignore_case = self.ignore_case.isChecked() @@ -93,8 +114,8 @@ class FilterWidget(QWidget): if len(query) == 0: return - if self.filter_task: - self.filter_task.aborted = True + # cancel previous search + self._cancel_search() try: flags = re.IGNORECASE if ignore_case else 0 @@ -106,5 +127,15 @@ class FilterWidget(QWidget): # query was not a valid regex -> abort return - self.filter_task = FilterTask(self.source_model, self.filter_model, regex) + + + + self.filter_task = FilterTask( + self.source_model, + self.filter_model, + regex, + self._lock, + lambda : self.btn_cancel_search.setVisible(True), + lambda : self.btn_cancel_search.setVisible(False) + ) QThreadPool.globalInstance().start(self.filter_task)