Show progress while processing logs

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-17 20:47:32 +08:00
parent 394f5c6313
commit bcefbe6b86
3 changed files with 111 additions and 10 deletions
+12 -4
View File
@@ -3018,16 +3018,24 @@ TRANSLATION = {
"source": [
"Furious.Window.LogPage"
],
"RU": "Автопрокрутка вниз",
"ZH": "自动滚动到底部",
"isReviewed": "True",
"RU": "Автопрокрутка вниз"
"isReviewed": "True"
},
"Auto Clear Log": {
"source": [
"Furious.Window.LogPage"
],
"RU": "Автоочистка журнала",
"ZH": "自动清理日志",
"isReviewed": "True",
"RU": "Автоочистка журнала"
"isReviewed": "True"
},
"Processing...": {
"source": [
"Furious.Window.LogPage"
],
"RU": "Обработка...",
"ZH": "处理中...",
"isReviewed": "True"
}
}
+91 -6
View File
@@ -34,8 +34,9 @@ from Furious.Service.LogManager import (
LogManager,
formatLogEntry,
)
from Furious.Widget.WaitingSpinner import WaitingSpinner
from PySide6 import QtCore
from PySide6 import QtCore, QtGui
from PySide6.QtGui import QTextCursor
from PySide6.QtWidgets import *
@@ -122,7 +123,10 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
"""Present and filter the unified application log stream as a page."""
UpdateDelay = 25
HighlightBatchSize = 250
HighlightBatchSize = 64
HighlightTimeBudget = 8
HighlightBusyThreshold = 100
BusyIndicatorDelay = 16
TailTolerance = 10
def __init__(self, *args, **kwargs):
@@ -161,6 +165,34 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
self.textBrowser.setUndoRedoEnabled(False)
self.textBrowser.document().setMaximumBlockCount(manager.maximumEntries)
self.highlightOverlay = QFrame(self.textBrowser.viewport())
self.highlightOverlay.setObjectName('LogHighlightOverlay')
self.highlightOverlay.setAutoFillBackground(True)
self.highlightOverlay.setBackgroundRole(QtGui.QPalette.ColorRole.Base)
self.highlightOverlay.setAttribute(
QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents
)
self.highlightSpinner = WaitingSpinner(
self.highlightOverlay,
center_on_parent=False,
lines=12,
line_length=7,
line_width=3,
radius=5,
)
self.highlightStatusLabel = AppQLabel(translatable=False)
highlightLayout = QHBoxLayout(self.highlightOverlay)
highlightLayout.setContentsMargins(16, 16, 16, 16)
highlightLayout.setSpacing(10)
highlightLayout.addStretch(1)
highlightLayout.addWidget(self.highlightSpinner)
highlightLayout.addWidget(self.highlightStatusLabel)
highlightLayout.addStretch(1)
self.highlightOverlay.hide()
self._entriesDirty = True
self._representationInvalid = True
self._renderedSequence = 0
@@ -379,12 +411,37 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
if not self._pageCanRender():
return
if self._representationInvalid:
self._setHighlightBusy(True)
if immediate:
self._updateTimer.stop()
self._updateTimer.start(0)
self._updateTimer.start(
self.BusyIndicatorDelay if self.highlightSpinner.is_spinning else 0
)
elif not self._updateTimer.isActive():
self._updateTimer.start(self.UpdateDelay)
def _syncHighlightOverlayGeometry(self):
"""Cover the log viewport without affecting its layout or scrollbars."""
self.highlightOverlay.setGeometry(self.textBrowser.viewport().rect())
def _setHighlightBusy(self, busy: bool):
"""Show or hide the cooperative highlighting progress presentation."""
if busy and self._pageCanRender():
self.highlightSpinner.color = self.palette().color(
QtGui.QPalette.ColorRole.Text
)
self._syncHighlightOverlayGeometry()
self.highlightOverlay.raise_()
self.highlightOverlay.show()
self.highlightSpinner.start()
else:
self.highlightSpinner.stop()
self.highlightOverlay.hide()
@staticmethod
def _incrementalPlan(oldSequences, currentSequences):
"""Return leading removals and the first new snapshot entry.
@@ -549,6 +606,8 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
if firstHighlightBlock is not None:
self._scheduleHighlight(firstHighlightBlock)
else:
self._setHighlightBusy(False)
if self._followTail:
self._scheduleScrollRestore()
@@ -579,6 +638,12 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
else:
self._highlightNextBlock = min(self._highlightNextBlock, firstBlock)
if (
self.textBrowser.document().blockCount() - self._highlightNextBlock
>= self.HighlightBusyThreshold
):
self._setHighlightBusy(True)
if self._pageCanRender() and not self._highlightTimer.isActive():
self._highlightTimer.start(0)
@@ -590,19 +655,28 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
document = self.textBrowser.document()
start = self._highlightNextBlock
end = min(document.blockCount(), start + self.HighlightBatchSize)
maximumEnd = min(document.blockCount(), start + self.HighlightBatchSize)
elapsed = QtCore.QElapsedTimer()
elapsed.start()
end = start
for blockNumber in range(start, end):
block = document.findBlockByNumber(blockNumber)
while end < maximumEnd:
block = document.findBlockByNumber(end)
if block.isValid():
self.textBrowser.rehighlightBlock(block)
end += 1
if elapsed.elapsed() >= self.HighlightTimeBudget:
break
if end < document.blockCount():
self._highlightNextBlock = end
self._highlightTimer.start(0)
else:
self._highlightNextBlock = None
self._setHighlightBusy(False)
if self._followTail:
self._scheduleScrollRestore()
@@ -724,8 +798,10 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
super().showEvent(event)
if self._entriesDirty:
self._setHighlightBusy(True)
self._requestRefresh(immediate=True)
elif self._highlightNextBlock is not None:
self._setHighlightBusy(True)
self._highlightTimer.start(0)
if self._followTail:
@@ -738,9 +814,17 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
self._highlightTimer.stop()
self._scrollTimer.stop()
self._followStateTimer.stop()
self._setHighlightBusy(False)
super().hideEvent(event)
def resizeEvent(self, event):
"""Keep the highlighting overlay aligned with the log viewport."""
super().resizeEvent(event)
if hasattr(self, 'highlightOverlay'):
self._syncHighlightOverlayGeometry()
def plainText(self) -> str:
"""Return the plain text currently shown by the selected filter."""
return self.textBrowser.toPlainText()
@@ -758,6 +842,7 @@ class LogPage(Mixins.QTranslatable, QMainWindow):
self.filterLabel.setText(_('Log Type'))
self.autoScrollLabel.setText(_('Auto Scroll Down'))
self.autoClearLabel.setText(_('Auto Clear Log'))
self.highlightStatusLabel.setText(_('Processing...'))
self._populateFilters(selectedCategoryId)
self._requestRefresh(immediate=True)
+8
View File
@@ -262,9 +262,14 @@ class UnifiedLogPageTest(unittest.TestCase):
# build and highlight thousands of QTextDocument blocks.
self.assertTrue(page._entriesDirty)
self.assertEqual(page.textBrowser.toPlainText(), '')
self.assertTrue(page.highlightOverlay.isVisible())
self.assertTrue(page.highlightSpinner.is_spinning)
self.assertRendered(page)
self.assertFalse(page.highlightOverlay.isVisible())
self.assertFalse(page.highlightSpinner.is_spinning)
lines = page.textBrowser.toPlainText().splitlines()
self.assertEqual(len(lines), entryCount)
@@ -630,6 +635,9 @@ class UnifiedLogPageTest(unittest.TestCase):
page._followStateTimer,
page.autoScrollSwitch,
page.autoClearSwitch,
page.highlightOverlay,
page.highlightSpinner,
page.highlightStatusLabel,
)
)