rename ravenlog to krowlog

There is a database named RavenDB.
KrowLog starts with a K, which is a) distinctive and b) has an association to KDE.
This commit is contained in:
2022-02-12 10:22:47 +01:00
parent 38e14d6042
commit a640b35c87
62 changed files with 380 additions and 362 deletions

View File

View File

@@ -0,0 +1,21 @@
from src.ui.bigtext.bigtext import BigText
class FilterViewSyncer:
def __init__(self, sync_view: BigText):
self._matches = {}
self._sync_view = sync_view
def click_listener(self, byte_offset: int):
source_byte_offset = self._matches[byte_offset] if byte_offset in self._matches else None
# print("click %d -> %d (total hits %d)" % (byte_offset, source_byte_offset, len(self._matches)))
if source_byte_offset is not None:
self._sync_view.scroll_to_byte(source_byte_offset)
def match_found(self, match_byte_offset: int, source_byte_offset: int):
# print("match %d" % match_byte_offset)
if match_byte_offset >= 0:
self._matches[match_byte_offset] = source_byte_offset
else:
self._matches = {}

View File

@@ -0,0 +1,246 @@
import logging
import os
import re
import tempfile
import threading
import time
from typing import Optional, Callable
from PySide6.QtCore import QRunnable, QThreadPool, Signal, QThread, QObject
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QCheckBox, QPushButton, QLabel, QProgressBar
from src.ui.bigtext.bigtext import BigText
from src.ui.bigtext.logFileModel import LogFileModel
from src.i18n import _
from src.pluginregistry import PluginRegistry
log = logging.getLogger("filterwidget")
class FilterTask(QThread):
aborted = False
on_before = Signal()
on_finish = Signal()
filter_progress = Signal(float)
def __init__(
self,
source_model: LogFileModel,
filter_model: LogFileModel,
regex: re.Pattern,
lock: threading.RLock,
filter_match_found_listeners: Callable[[int], None]
):
super(FilterTask, self).__init__()
self.source_model = source_model
self.filter_model = filter_model
self.regex = regex
self.lock = lock
self.filter_match_found_listeners = filter_match_found_listeners
def run(self):
# print("writing to tmp file", self.filter_model.get_file())
# the lock ensures that we only start a new search when the previous search already ended
with self.lock:
print("starting thread ", threading.current_thread())
self.on_before.emit()
if self.aborted:
self.on_finish.emit()
for listener in self.filter_match_found_listeners:
listener(-1, -1) # notify listeners that a new search started
self.filter_progress.emit(0.0)
try:
source_file = self.source_model.get_file()
file_size = os.stat(source_file).st_size
start = time.time()
with open(source_file, "rb") as source:
with open(self.filter_model.get_file(), "w+b") as target:
line_count = 0
lines_written = 0
last_bytes_read = 0
bytes_read = 0
while l := source.readline():
bytes_read = bytes_read + len(l)
line_count = line_count + 1
line = l.decode("utf8", errors="ignore")
if self.regex.findall(line):
# time.sleep(0.5)
lines_written = lines_written + 1
source_line_offset = source.tell() - len(l)
target_line_offset = target.tell()
for listener in self.filter_match_found_listeners:
listener(target_line_offset, source_line_offset)
target.write(l)
# sometime buffering can hide results for a while
# We force a flush periodically.
if line_count % 10000 == 0:
now = time.time()
time_diff = (now - start)
if time_diff > 0.5:
read_speed = ((bytes_read - last_bytes_read) / time_diff) / (1024 * 1024)
# todo progress disabled because of its detrimental effect on UI responsibility
# print("emit %f" % (bytes_read / file_size))
self.filter_progress.emit(bytes_read / file_size)
# self._progress_updater(bytes_read / file_size, read_speed)
last_bytes_read = bytes_read
start = time.time()
if lines_written > 0:
target.flush()
lines_written = 0
if self.aborted:
print("aborted ", time.time())
break
finally:
self.on_finish.emit()
print("dome thread ", threading.current_thread())
class FilterWidget(QWidget):
filter_model: LogFileModel
filter_task: Optional[FilterTask] = None
def __init__(self, source_model: LogFileModel):
super(FilterWidget, self).__init__()
self.source_model = source_model
self._lock = threading.RLock()
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.query_field = QLineEdit()
self.query_field.textChanged.connect(self.filter_changed)
self._lbl_search_progress = QLabel("0%")
self._lbl_search_progress.setVisible(False)
self._progress_bar = QProgressBar();
self._progress_bar.setVisible(False)
self._progress_bar.setMinimum(0)
self._progress_bar.setMaximum(100)
self._progress_bar.setMaximumWidth(50)
self.btn_cancel_search = QPushButton(_("Cancel"))
self.btn_cancel_search.setVisible(False)
self.btn_cancel_search.pressed.connect(self._cancel_search)
self.ignore_case = QCheckBox(_("ignore case"))
self.ignore_case.setChecked(True)
self.ignore_case.stateChanged.connect(self.filter_changed)
self.is_regex = QCheckBox(_("regex"))
self.is_regex.setChecked(True)
self.is_regex.stateChanged.connect(self.filter_changed)
filter_bar = 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._lbl_search_progress)
filter_bar.layout.addWidget(self._progress_bar)
filter_bar.layout.addWidget(self.btn_cancel_search)
filter_bar.layout.addWidget(self.ignore_case)
filter_bar.layout.addWidget(self.is_regex)
(handle, self.tmpfilename) = tempfile.mkstemp()
os.close(handle)
self.filter_model = LogFileModel(self.tmpfilename, self.source_model.settings)
self.hits_view = BigText(self.filter_model)
self.layout.addWidget(filter_bar)
self.layout.addWidget(self.hits_view)
self.filter_match_found_listeners: [Callable[[int], None]] = []
def add_line_click_listener(self, listener: Callable[[int], None]):
self.hits_view.add_line_click_listener(listener)
def add_filter_match_found_listener(self, listener: Callable[[int], None]):
self.filter_match_found_listeners.append(listener)
def destruct(self):
# print("cleanup: ", self.tmpfilename)
self._cancel_search()
os.remove(self.tmpfilename)
def _cancel_search(self):
if self.filter_task:
# print("cancel started ", time.time())
self.filter_task.aborted = True
# wait until the previous search is aborted
with self._lock:
pass
def reset_filter(self):
self.filter_model.truncate()
self.source_model.clear_query_highlight()
self.filter_model.clear_query_highlight()
PluginRegistry.execute("update_ui")
def filter_changed(self):
query = self.query_field.text()
ignore_case = self.ignore_case.isChecked()
is_regex = self.is_regex.isChecked()
# cancel previous search
self._cancel_search()
if len(query) == 0:
self.reset_filter()
return
try:
flags = re.IGNORECASE if ignore_case else 0
if is_regex:
regex = re.compile(query, flags=flags)
else:
regex = re.compile(re.escape(query), flags=flags)
except:
# query was not a valid regex -> clear search hits, then abort
self.filter_model.truncate()
return
self.source_model.set_query_highlight(query, ignore_case, is_regex)
self.filter_model.set_query_highlight(query, ignore_case, is_regex)
self.filter_task = FilterTask(
self.source_model,
self.filter_model,
regex,
self._lock,
self.filter_match_found_listeners,
)
self.filter_task.on_before.connect(self._on_before)
self.filter_task.on_finish.connect(self._on_finish)
self.filter_task.filter_progress.connect(self._update_progress)
# self.filter_task.finished.connect(self.filter_task.deleteLater)
# super().connect(self.filter_task, FilterTask.filter_progress, self, self._update_progress)
# super().connect(self.filter_task, FilterTask.finished, self.filter_task, QObject.deleteLater)
self.filter_task.start()
# QThreadPool.globalInstance().start(self.filter_task)
def _on_before(self):
print("on_before")
self.btn_cancel_search.setVisible(True)
self._progress_bar.setVisible(True)
def _on_finish(self):
print("on_finish")
self.btn_cancel_search.setVisible(False)
self._progress_bar.setVisible(False)
self.filter_task.deleteLater()
def _update_progress(self, progress: float):
print("progress %f" % (progress))
self._progress_bar.setValue(progress * 100)

View File

@@ -0,0 +1,47 @@
from PySide6.QtWidgets import *
from PySide6.QtCore import *
from src.ui.bigtext.bigtext import BigText
from src.plugins.logfile.filterviewsyncer import FilterViewSyncer
from src.plugins.logfile.filterwidget import FilterWidget
from src.ui.bigtext.logFileModel import LogFileModel
from src.plugins.krowlog.Tab import Tab
from src.util.conversion import humanbytes
class FullTabWidget(Tab):
def __init__(self, model: LogFileModel, unique_id: str, title: str):
super(FullTabWidget, self).__init__(unique_id, title)
self._model = model
self.file_view = BigText(model)
self.filter_hit_view = FilterWidget(self._model)
self.filter_view_syncer = FilterViewSyncer(self.file_view)
self.filter_hit_view.add_line_click_listener(self.filter_view_syncer.click_listener)
self.filter_hit_view.add_filter_match_found_listener(self.filter_view_syncer.match_found)
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
splitter = QSplitter()
splitter.setOrientation(Qt.Orientation.Vertical)
splitter.setHandleWidth(5)
# splitter.setStyleSheet("QSplitter::handle{background: #cccccc;}")
splitter.addWidget(self.file_view)
splitter.addWidget(self.filter_hit_view)
self.layout.addWidget(splitter)
def get_file(self) -> str:
return self.file_view.get_file()
# overriding abstract method
def destruct(self):
self.file_view.destruct()
self.filter_hit_view.destruct()
# overriding abstract method
def get_status_text(self) -> str:
file = self._model.get_file()
file_size = humanbytes(self._model.byte_count())
return "%s - %s" % (file_size, file)