Update writing stats dialog to work with new log file
This commit is contained in:
@@ -104,8 +104,7 @@ def checkBool(value: Any, default: bool) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def checkHandle(value, default, allowNone=False):
|
def checkHandle(value, default, allowNone=False):
|
||||||
"""Check if a value is a handle.
|
"""Check if a value is a handle."""
|
||||||
"""
|
|
||||||
if allowNone and (value is None or value == "None"):
|
if allowNone and (value is None or value == "None"):
|
||||||
return None
|
return None
|
||||||
if isHandle(value):
|
if isHandle(value):
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from novelwriter.error import logException
|
from novelwriter.error import logException
|
||||||
@@ -109,6 +109,20 @@ class NWSessionLog:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def iterRecords(self) -> Iterator[dict]:
|
||||||
|
"""Iterate through all records in the log."""
|
||||||
|
sessFile = self._project.storage.getMetaFile(nwFiles.SESS_FILE)
|
||||||
|
if not isinstance(sessFile, Path):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
with open(sessFile, mode="r", encoding="utf-8") as fObj:
|
||||||
|
for line in fObj:
|
||||||
|
yield json.loads(line)
|
||||||
|
except Exception:
|
||||||
|
logger.error("Failed to process session stats file")
|
||||||
|
logException()
|
||||||
|
return
|
||||||
|
|
||||||
def createInitial(self, total: int) -> str:
|
def createInitial(self, total: int) -> str:
|
||||||
"""Low level function to create the initial log file record."""
|
"""Low level function to create the initial log file record."""
|
||||||
data = json.dumps({"type": "initial", "offset": total})
|
data = json.dumps({"type": "initial", "offset": total})
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
novelWriter – GUI Writing Statistics
|
novelWriter – GUI Writing Statistics
|
||||||
====================================
|
====================================
|
||||||
GUI class for the session statistics dialog
|
|
||||||
|
|
||||||
File History:
|
File History:
|
||||||
Created: 2019-10-20 [0.3]
|
Created: 2019-10-20 [0.3]
|
||||||
@@ -22,12 +21,13 @@ General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtGui import QPixmap, QCursor
|
from PyQt5.QtGui import QPixmap, QCursor
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
@@ -40,13 +40,20 @@ from novelwriter import CONFIG
|
|||||||
from novelwriter.enum import nwAlert
|
from novelwriter.enum import nwAlert
|
||||||
from novelwriter.error import formatException
|
from novelwriter.error import formatException
|
||||||
from novelwriter.common import formatTime, checkInt, checkIntTuple, minmax
|
from novelwriter.common import formatTime, checkInt, checkIntTuple, minmax
|
||||||
from novelwriter.constants import nwConst, nwFiles
|
from novelwriter.constants import nwConst
|
||||||
from novelwriter.extensions.switch import NSwitch
|
from novelwriter.extensions.switch import NSwitch
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
from novelwriter.guimain import GuiMain
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GuiWritingStats(QDialog):
|
class GuiWritingStats(QDialog):
|
||||||
|
"""GUI Tools: Writing Statistics
|
||||||
|
|
||||||
|
Displays data from the NWSessionLog object.
|
||||||
|
"""
|
||||||
|
|
||||||
C_TIME = 0
|
C_TIME = 0
|
||||||
C_LENGTH = 1
|
C_LENGTH = 1
|
||||||
@@ -57,7 +64,7 @@ class GuiWritingStats(QDialog):
|
|||||||
FMT_JSON = 0
|
FMT_JSON = 0
|
||||||
FMT_CSV = 1
|
FMT_CSV = 1
|
||||||
|
|
||||||
def __init__(self, mainGui):
|
def __init__(self, mainGui: GuiMain):
|
||||||
super().__init__(parent=mainGui)
|
super().__init__(parent=mainGui)
|
||||||
|
|
||||||
logger.debug("Create: GuiWritingStats")
|
logger.debug("Create: GuiWritingStats")
|
||||||
@@ -295,8 +302,7 @@ class GuiWritingStats(QDialog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def populateGUI(self):
|
def populateGUI(self):
|
||||||
"""Populate list box with data from the log file.
|
"""Populate list box with data from the log file."""
|
||||||
"""
|
|
||||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||||
self._loadLogFile()
|
self._loadLogFile()
|
||||||
self._updateListBox()
|
self._updateListBox()
|
||||||
@@ -308,8 +314,7 @@ class GuiWritingStats(QDialog):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _doClose(self):
|
def _doClose(self):
|
||||||
"""Save the state of the window, clear cache, end close.
|
"""Save the state of the window, clear cache, end close."""
|
||||||
"""
|
|
||||||
self.logData = []
|
self.logData = []
|
||||||
|
|
||||||
winWidth = CONFIG.rpxInt(self.width())
|
winWidth = CONFIG.rpxInt(self.width())
|
||||||
@@ -350,8 +355,7 @@ class GuiWritingStats(QDialog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _saveData(self, dataFmt):
|
def _saveData(self, dataFmt):
|
||||||
"""Save the content of the list box to a file.
|
"""Save the content of the list box to a file."""
|
||||||
"""
|
|
||||||
fileExt = ""
|
fileExt = ""
|
||||||
textFmt = ""
|
textFmt = ""
|
||||||
|
|
||||||
@@ -424,8 +428,7 @@ class GuiWritingStats(QDialog):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _loadLogFile(self):
|
def _loadLogFile(self):
|
||||||
"""Load the content of the log file into a buffer.
|
"""Load the content of the log file into a buffer."""
|
||||||
"""
|
|
||||||
logger.debug("Loading session log file")
|
logger.debug("Loading session log file")
|
||||||
|
|
||||||
self.logData = []
|
self.logData = []
|
||||||
@@ -436,50 +439,30 @@ class GuiWritingStats(QDialog):
|
|||||||
ttTime = 0
|
ttTime = 0
|
||||||
ttIdle = 0
|
ttIdle = 0
|
||||||
|
|
||||||
logFile = self.theProject.storage.getMetaFile(nwFiles.SESS_STATS)
|
for record in self.theProject.session.iterRecords():
|
||||||
if not isinstance(logFile, Path) or not logFile.exists():
|
rType = record.get("type")
|
||||||
logger.info("This project has no writing stats logfile")
|
if rType == "initial":
|
||||||
return False
|
self.wordOffset = checkInt(record.get("offset"), 0)
|
||||||
|
logger.debug("Initial word count when log was started is %d" % self.wordOffset)
|
||||||
|
elif rType == "record":
|
||||||
|
try:
|
||||||
|
dStart = datetime.fromisoformat(str(record.get("start")))
|
||||||
|
dEnd = datetime.fromisoformat(str(record.get("end")))
|
||||||
|
except Exception:
|
||||||
|
logger.error("Invalid session log record")
|
||||||
|
continue
|
||||||
|
wcNovel = checkInt(record.get("novel"), 0)
|
||||||
|
wcNotes = checkInt(record.get("notes"), 0)
|
||||||
|
sIdle = checkInt(record.get("idle"), 0)
|
||||||
|
|
||||||
try:
|
tDiff = dEnd - dStart
|
||||||
with open(logFile, mode="r", encoding="utf-8") as inFile:
|
sDiff = tDiff.total_seconds()
|
||||||
for inLine in inFile:
|
ttTime += sDiff
|
||||||
if inLine.startswith("#"):
|
ttIdle += sIdle
|
||||||
if inLine.startswith("# Offset"):
|
ttNovel = wcNovel
|
||||||
self.wordOffset = checkInt(inLine[9:].strip(), 0)
|
ttNotes = wcNotes
|
||||||
logger.debug(
|
|
||||||
"Initial word count when log was started is %d" % self.wordOffset
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
inData = inLine.split()
|
self.logData.append((dStart, sDiff, wcNovel, wcNotes, sIdle))
|
||||||
if len(inData) < 6:
|
|
||||||
continue
|
|
||||||
|
|
||||||
dStart = datetime.fromisoformat(" ".join(inData[0:2]))
|
|
||||||
dEnd = datetime.fromisoformat(" ".join(inData[2:4]))
|
|
||||||
|
|
||||||
sIdle = 0
|
|
||||||
if len(inData) > 6:
|
|
||||||
sIdle = checkInt(inData[6], 0)
|
|
||||||
|
|
||||||
tDiff = dEnd - dStart
|
|
||||||
sDiff = tDiff.total_seconds()
|
|
||||||
ttTime += sDiff
|
|
||||||
ttIdle += sIdle
|
|
||||||
|
|
||||||
wcNovel = int(inData[4])
|
|
||||||
wcNotes = int(inData[5])
|
|
||||||
ttNovel = wcNovel
|
|
||||||
ttNotes = wcNotes
|
|
||||||
|
|
||||||
self.logData.append((dStart, sDiff, wcNovel, wcNotes, sIdle))
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
self.mainGui.makeAlert(self.tr(
|
|
||||||
"Failed to read session log file."
|
|
||||||
), nwAlert.ERROR, exception=exc)
|
|
||||||
return False
|
|
||||||
|
|
||||||
ttWords = ttNovel + ttNotes
|
ttWords = ttNovel + ttNotes
|
||||||
self.labelTotal.setText(formatTime(round(ttTime)))
|
self.labelTotal.setText(formatTime(round(ttTime)))
|
||||||
@@ -495,8 +478,7 @@ class GuiWritingStats(QDialog):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _updateListBox(self):
|
def _updateListBox(self):
|
||||||
"""Load/reload the content of the list box.
|
"""Load/reload the content of the list box."""
|
||||||
"""
|
|
||||||
self.listBox.clear()
|
self.listBox.clear()
|
||||||
self.timeFilter = 0.0
|
self.timeFilter = 0.0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user