I'm developing an application with PySide2 and I need to display the content of an HTML file which is a sort of log file, so it is continuously changing. I need to see these changes in realtime on my application. Is there a smart and fast way to do so? Currently I'm continuously reading from the file and comparing last lines to see if there is a new line: if so I append it to the QTextEdit widget.
I thought that maybe Qt provides some functionalities to achieve this in a smarter way.
CODE SNIPPET
path = some_file.html
with open(path) as file:
old = file.readlines()
try:
last_line = old[-1]
except IndexError:
last_line = ''
while streaming:
line = file.readline()
if line and line != last_line:
qtext_edit.append(line)
last_line = line
After some digging I came out with a solution that fits my needs.
As suggested in the comments, the best way could have been to exploit QFileSystemWatcher
to trigger a signal every time the file is changed by using the fileChanged
signal. The problem, in my case, is that the fileChanged
signal is not triggered continuously as I expected (the file is being continuously written) and so the QTextEdit
doesn't update as I expect.
Therefore the possible best solution has been discarded.
I decided to re-visit my old function and try to optimize it. Here's the code:
DATALOG_UPDATE_TIME = 0.1
class DatalogSignals(QObject):
output = Signal(list)
class DatalogStreamer(QRunnable):
def __init__(self, streaming: bool, datalog: str):
super().__init__()
self.streaming = streaming
self.datalog = datalog
self.signals = DatalogSignals()
def run(self):
with open(self.datalog) as file:
file.readlines()
while self.streaming:
time.sleep(DATALOG_UPDATE_TIME)
lines = file.readlines()
if lines:
self.signals.output.emit(lines)
LOG.debug('Datalog streaming finished')
def stop(self):
self.streaming = False
Note: the code I posted in the Question was in the run()
function of QRunnable
as well, but I didn't post it to make it easier to understand. Now I'm posting it integrally because I think that full code could be helpful to someone.
As you can notice, I exploited a feature of readlines()
: after the first call, it starts to read from the last line it read. This means that if you called this function once, the second time you call readlines()
(keeping the file opened) and the file didn't change, you'd get an empty list; whereas if the file changed, you would get a list with just the new lines and not all the file lines.
An object of the class DatalogStreamer
is created in the main class and started in a QThreadPool
(in order to not execute the code in the main thread and therefore not block the GUI). This is the method to start the streaming:
def _start_datalog_streaming(self):
self.datalog_streamer = DatalogStreamer(not self.actionDisable.isChecked(), self.datalog_path)
self.datalog_streamer.signals.output.connect(self.datalog_editor.update_datalog)
self.thread_pool.start(self.datalog_streamer)
As you can see the output
signal (the one that emits the new lines) is connected to method of the object datalog_editor
(class DatalogEdit
, which is nothing but a subclass of QTextEdit
).
Here's the method's implementation:
def update_datalog(self, lines):
self.moveCursor(QTextCursor.End)
for line in lines:
self.insertHtml(line.replace("\t", " ") + "<br>")
self.verticalScrollBar().setValue(self.verticalScrollBar().maximum())
I used insertHtml()
because lines (list of strings) can contain html tags. Moreover, I replaced "\t" with 4 spaces (* ) as it was ignored and add a newline after every line (
*).
With this simple solution I have a solved three problems:
QTextEdit
is now formatted as the one in the HTML file