try to fix ui freeze when filtering huge files

This commit is contained in:
2021-10-28 08:45:24 +02:00
parent e426b816ef
commit ce840819b2
4 changed files with 31 additions and 23 deletions

View File

@@ -42,15 +42,13 @@ class FileObserver(FileSystemEventHandler):
class FileWatchdogThread(QRunnable): class FileWatchdogThread(QRunnable):
observer = None
def __init__(self, big_text, file: str): def __init__(self, big_text, file: str):
super(FileWatchdogThread, self).__init__() super(FileWatchdogThread, self).__init__()
self.file = file self.file = file
self.big_text = big_text self.big_text = big_text
self.observer = Observer()
def run(self) -> None: def run(self) -> None:
self.observer = Observer()
self.observer.schedule(FileObserver(self.big_text), self.file) self.observer.schedule(FileObserver(self.big_text), self.file)
self.observer.start() self.observer.start()
@@ -238,7 +236,7 @@ class InnerBigText(QWidget):
cb.setText(selected_text) cb.setText(selected_text)
def paintEvent(self, event: QPaintEvent) -> None: def paintEvent(self, event: QPaintEvent) -> None:
#print("paintEvent") # print("paintEvent")
painter = QPainter(self) painter = QPainter(self)
painter.setFont(self.model.settings.font()) painter.setFont(self.model.settings.font())
painter.setPen(QColor(0, 0, 0)) painter.setPen(QColor(0, 0, 0))

View File

@@ -10,11 +10,10 @@ from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QCheck
from bigtext import BigText from bigtext import BigText
from logFileModel import LogFileModel from logFileModel import LogFileModel
import asyncio
class FilterTask(QRunnable): class FilterTask(QRunnable):
aborted = False aborted = False
future: asyncio.Future
def __init__( def __init__(
self, self,
@@ -38,33 +37,46 @@ class FilterTask(QRunnable):
# the lock ensures that we only start a new search when the previous search already ended # the lock ensures that we only start a new search when the previous search already ended
with self.lock: with self.lock:
#print("starting thread ", threading.currentThread())
self.on_before() self.on_before()
try: try:
with open(self.source_model.get_file(), "rb") as source: with open(self.source_model.get_file(), "rb") as source:
with open(self.filter_model.get_file(), "w+b") as target: with open(self.filter_model.get_file(), "w+b") as target:
line_count = 0
lines_written = 0
while l := source.readline(): while l := source.readline():
line_count = line_count + 1
line = l.decode("utf8", errors="ignore") line = l.decode("utf8", errors="ignore")
if self.regex.findall(line): if self.regex.findall(line):
#time.sleep(0.5)
lines_written = lines_written +1
target.write(line.encode("utf8")) target.write(line.encode("utf8"))
# sometime buffering can hide results for a while
# We force a flush periodically.
if line_count % 10000 == 0 and lines_written > 0:
target.flush()
lines_written = 0
if self.aborted: if self.aborted:
#print("aborted ", time.time()) # print("aborted ", time.time())
break break
finally: finally:
self.on_finish() self.on_finish()
#print("dome thread ", threading.currentThread())
class FilterWidget(QWidget): class FilterWidget(QWidget):
future = None
filter_model: LogFileModel filter_model: LogFileModel
filter_task: Optional[FilterTask] = None filter_task: Optional[FilterTask] = None
_lock = threading.RLock()
def __init__(self, source_model: LogFileModel): def __init__(self, source_model: LogFileModel):
super(FilterWidget, self).__init__() super(FilterWidget, self).__init__()
self.source_model = source_model self.source_model = source_model
self._lock = threading.RLock()
self.layout = QVBoxLayout(self) self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setContentsMargins(0, 0, 0, 0)
@@ -99,18 +111,17 @@ class FilterWidget(QWidget):
self.layout.addWidget(self.hits_view) self.layout.addWidget(self.hits_view)
def destruct(self): def destruct(self):
#print("cleanup: ", self.tmpfilename) # print("cleanup: ", self.tmpfilename)
os.remove(self.tmpfilename) os.remove(self.tmpfilename)
def _cancel_search(self): def _cancel_search(self):
if self.filter_task: if self.filter_task:
#print("cancel started ", time.time()) # print("cancel started ", time.time())
self.filter_task.aborted = True self.filter_task.aborted = True
def filter_changed(self): def filter_changed(self):
query = self.query_field.text() query = self.query_field.text()
ignore_case = self.ignore_case.isChecked() ignore_case = self.ignore_case.isChecked()
is_regex = self.is_regex.isChecked()
if len(query) == 0: if len(query) == 0:
return return
@@ -119,7 +130,7 @@ class FilterWidget(QWidget):
try: try:
flags = re.IGNORECASE if ignore_case else 0 flags = re.IGNORECASE if ignore_case else 0
if is_regex: if self.is_regex.isChecked():
regex = re.compile(query, flags=flags) regex = re.compile(query, flags=flags)
else: else:
regex = re.compile(re.escape(query), flags=flags) regex = re.compile(re.escape(query), flags=flags)
@@ -127,15 +138,12 @@ class FilterWidget(QWidget):
# query was not a valid regex -> abort # query was not a valid regex -> abort
return return
self.filter_task = FilterTask( self.filter_task = FilterTask(
self.source_model, self.source_model,
self.filter_model, self.filter_model,
regex, regex,
self._lock, self._lock,
lambda : self.btn_cancel_search.setVisible(True), lambda: self.btn_cancel_search.setVisible(True),
lambda : self.btn_cancel_search.setVisible(False) lambda: self.btn_cancel_search.setVisible(False)
) )
QThreadPool.globalInstance().start(self.filter_task) QThreadPool.globalInstance().start(self.filter_task)

View File

@@ -1,5 +1,6 @@
import math import math
import threading import threading
import time
from typing import List from typing import List
from line import Line from line import Line
import os import os
@@ -7,11 +8,11 @@ from settings import Settings
class LogFileModel: class LogFileModel:
_lock = threading.RLock()
def __init__(self, file:str, settings: Settings): def __init__(self, file:str, settings: Settings):
self.settings = settings self.settings = settings
self._file = os.path.realpath(file) self._file = os.path.realpath(file)
self._lock = threading.RLock()
def get_file(self): def get_file(self):
return self._file return self._file
@@ -36,11 +37,12 @@ class LogFileModel:
#print("data(%s, %s, %s)" % (byte_offset, scroll_lines, lines)) #print("data(%s, %s, %s)" % (byte_offset, scroll_lines, lines))
lines_before_offset: List[Line] = [] lines_before_offset: List[Line] = []
lines_after_offset: List[Line] = [] lines_after_offset: List[Line] = []
result: List[Line] = []
lines_to_find = lines + abs(scroll_lines) lines_to_find = lines + abs(scroll_lines)
lines_to_return = math.ceil(lines) lines_to_return = math.ceil(lines)
#start = time.time()
with self._lock: with self._lock:
#print("data lock acquision %.4f" % (time.time() -start))
# TODO handle lines longer than 4096 bytes # TODO handle lines longer than 4096 bytes
# TODO abort file open after a few secons: https://docs.python.org/3/library/signal.html#example # TODO abort file open after a few secons: https://docs.python.org/3/library/signal.html#example
with open(self._file, 'rb') as f: with open(self._file, 'rb') as f:

View File

@@ -114,7 +114,7 @@ if __name__ == "__main__":
# control back to python and allows python to react to signals # control back to python and allows python to react to signals
timer = QTimer() timer = QTimer()
timer.timeout.connect(lambda: None) timer.timeout.connect(lambda: None)
timer.start(500) timer.start(100)
window = MainWindow() window = MainWindow()
window.show() window.show()