Stop showing index rebuild dialog on upgrades (#2537)

This commit is contained in:
Veronica Berglyd Olsen
2025-10-05 17:52:40 +02:00
committed by GitHub
9 changed files with 61 additions and 26 deletions
+6
View File
@@ -560,6 +560,12 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
return "".join(buffer) return "".join(buffer)
def jsonCombine(data: dict[str, str]) -> str:
"""Combine multiple already packed JSON strings."""
payload = ",\n".join(f' "{k}": {v}' for k, v in data.items())
return f"{{\n{payload}\n}}\n"
## ##
# XML Helpers # XML Helpers
## ##
+24 -11
View File
@@ -33,8 +33,10 @@ from pathlib import Path
from time import time from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from novelwriter import SHARED from novelwriter import SHARED, __hexversion__
from novelwriter.common import isHandle, isItemClass, isTitleTag, jsonEncode from novelwriter.common import (
formatTimeStamp, isHandle, isItemClass, isTitleTag, jsonCombine, jsonEncode
)
from novelwriter.constants import nwFiles, nwKeyWords, nwStyles from novelwriter.constants import nwFiles, nwKeyWords, nwStyles
from novelwriter.core.indexdata import NOTE_TYPES, TT_NONE, IndexHeading, IndexNode, T_NoteTypes from novelwriter.core.indexdata import NOTE_TYPES, TT_NONE, IndexHeading, IndexNode, T_NoteTypes
from novelwriter.core.novelmodel import NovelModel from novelwriter.core.novelmodel import NovelModel
@@ -82,6 +84,11 @@ class Index:
a rebuild of the index data. a rebuild of the index data.
""" """
__slots__ = (
"_indexBroken", "_indexChange", "_indexUpgrade", "_itemIndex", "_novelExtra",
"_novelModels", "_project", "_rootChange", "_tagsIndex",
)
def __init__(self, project: NWProject) -> None: def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
@@ -90,6 +97,7 @@ class Index:
self._tagsIndex = TagsIndex() self._tagsIndex = TagsIndex()
self._itemIndex = ItemIndex(project, self._tagsIndex) self._itemIndex = ItemIndex(project, self._tagsIndex)
self._indexBroken = False self._indexBroken = False
self._indexUpgrade = False
# Models # Models
self._novelModels: dict[str, NovelModel] = {} self._novelModels: dict[str, NovelModel] = {}
@@ -110,6 +118,10 @@ class Index:
def indexBroken(self) -> bool: def indexBroken(self) -> bool:
return self._indexBroken return self._indexBroken
@property
def indexUpgrade(self) -> bool:
return self._indexUpgrade
## ##
# Getters # Getters
## ##
@@ -241,6 +253,8 @@ class Index:
return False return False
try: try:
meta = data.get("novelWriter.meta", {})
self._indexUpgrade = meta.get("version") != __hexversion__
self._tagsIndex.unpackData(data["novelWriter.tagsIndex"]) self._tagsIndex.unpackData(data["novelWriter.tagsIndex"])
self._itemIndex.unpackData(data["novelWriter.itemIndex"]) self._itemIndex.unpackData(data["novelWriter.itemIndex"])
except Exception: except Exception:
@@ -273,23 +287,22 @@ class Index:
return False return False
logger.debug("Saving index file") logger.debug("Saving index file")
tStart = time() start = time()
try: try:
tagsIndex = jsonEncode(self._tagsIndex.packData(), n=1, nmax=2) meta = {"version": __hexversion__, "timestamp": formatTimeStamp(start)}
itemIndex = jsonEncode(self._itemIndex.packData(), n=1, nmax=4)
with open(indexFile, mode="w+", encoding="utf-8") as outFile: with open(indexFile, mode="w+", encoding="utf-8") as outFile:
outFile.write("{\n") outFile.write(jsonCombine({
outFile.write(f' "novelWriter.tagsIndex": {tagsIndex},\n') "novelWriter.meta": jsonEncode(meta, n=1),
outFile.write(f' "novelWriter.itemIndex": {itemIndex}\n') "novelWriter.tagsIndex": jsonEncode(self._tagsIndex.packData(), n=1, nmax=2),
outFile.write("}\n") "novelWriter.itemIndex": jsonEncode(self._itemIndex.packData(), n=1, nmax=4),
}))
except Exception: except Exception:
logger.error("Failed to save index file") logger.error("Failed to save index file")
logException() logException()
return False return False
logger.debug("Index saved in %.3f ms", (time() - tStart)*1000) logger.debug("Index saved in %.3f ms", (time() - start)*1000)
return True return True
+3 -3
View File
@@ -497,7 +497,8 @@ class GuiMain(QMainWindow):
# Check if we need to rebuild the index # Check if we need to rebuild the index
if SHARED.project.index.indexBroken: if SHARED.project.index.indexBroken:
SHARED.info(self.tr("The project index is outdated or broken. Rebuilding index.")) if not SHARED.project.index.indexUpgrade:
SHARED.warn(self.tr("The project index is broken. Rebuilding index."))
self.rebuildIndex() self.rebuildIndex()
# Make sure the changed status is set to false on things opened # Make sure the changed status is set to false on things opened
@@ -729,7 +730,7 @@ class GuiMain(QMainWindow):
return return
def rebuildIndex(self, beQuiet: bool = False) -> None: def rebuildIndex(self) -> None:
"""Rebuild the entire index.""" """Rebuild the entire index."""
if SHARED.hasProject: if SHARED.hasProject:
logger.info("Rebuilding index ...") logger.info("Rebuilding index ...")
@@ -746,7 +747,6 @@ class GuiMain(QMainWindow):
self._updateStatusWordCount() self._updateStatusWordCount()
QApplication.restoreOverrideCursor() QApplication.restoreOverrideCursor()
if not beQuiet:
SHARED.info(self.tr("The project index has been successfully rebuilt.")) SHARED.info(self.tr("The project index has been successfully rebuilt."))
## ##
@@ -1,4 +1,8 @@
{ {
"novelWriter.meta": {
"version": "0x020800a2",
"timestamp": "2025-10-05 17:06:58"
},
"novelWriter.tagsIndex": { "novelWriter.tagsIndex": {
"bod": {"name": "Bod", "display": "Nobody Owens", "handle": "4c4f28287af27", "heading": "T0001", "class": "CHARACTER"}, "bod": {"name": "Bod", "display": "Nobody Owens", "handle": "4c4f28287af27", "heading": "T0001", "class": "CHARACTER"},
"main": {"name": "Main", "display": "Main", "handle": "2426c6f0ca922", "heading": "T0001", "class": "PLOT"}, "main": {"name": "Main", "display": "Main", "handle": "2426c6f0ca922", "heading": "T0001", "class": "PLOT"},
+15 -4
View File
@@ -36,10 +36,10 @@ from novelwriter.common import (
describeFont, elide, encodeMimeHandles, firstFloat, fontMatcher, describeFont, elide, encodeMimeHandles, firstFloat, fontMatcher,
formatFileFilter, formatInt, formatTime, formatTimeStamp, formatVersion, formatFileFilter, formatInt, formatTime, formatTimeStamp, formatVersion,
fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass, isItemLayout, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass, isItemLayout,
isItemType, isListInstance, isTitleTag, jsonEncode, makeFileNameSafe, isItemType, isListInstance, isTitleTag, jsonCombine, jsonEncode,
minmax, numberToRoman, openExternalPath, processDialogSymbols, makeFileNameSafe, minmax, numberToRoman, openExternalPath,
readTextFile, simplified, transferCase, uniqueCompact, utf16CharMap, processDialogSymbols, readTextFile, simplified, transferCase,
xmlElement, xmlIndent, xmlSubElem, yesNo uniqueCompact, utf16CharMap, xmlElement, xmlIndent, xmlSubElem, yesNo
) )
from novelwriter.enum import nwItemClass from novelwriter.enum import nwItemClass
@@ -651,6 +651,17 @@ def testBaseCommon_jsonEncode():
) )
@pytest.mark.base
def testBaseCommon_jsonCombine():
"""Test the jsonCombine function."""
assert jsonCombine({"a": "[1, 2]", "b": "[3, 4]"}) == (
'{\n'
' "a": [1, 2],\n'
' "b": [3, 4]\n'
'}\n'
)
@pytest.mark.base @pytest.mark.base
def testBaseCommon_xmlIndent(): def testBaseCommon_xmlIndent():
"""Test the xmlIndent function.""" """Test the xmlIndent function."""
+1 -1
View File
@@ -157,7 +157,7 @@ def testCoreIndex_LoadSave(qtbot, monkeypatch, prjLipsum, nwGUI, tstPaths):
# Check File # Check File
copyfile(projFile, testFile) copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile) assert cmpFiles(testFile, compFile, ignoreLines=[3, 4])
# Write an empty index file and load it # Write an empty index file and load it
projFile.write_text("{}", encoding="utf-8") projFile.write_text("{}", encoding="utf-8")
+5 -4
View File
@@ -32,7 +32,8 @@ from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPalette from PyQt6.QtGui import QPalette
from PyQt6.QtWidgets import QInputDialog, QMessageBox from PyQt6.QtWidgets import QInputDialog, QMessageBox
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED, __hexversion__
from novelwriter.common import jsonEncode
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.editlabel import GuiEditLabel
@@ -713,7 +714,7 @@ def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
cHandle = SHARED.project.newFile("Jane", C.hCharRoot) cHandle = SHARED.project.newFile("Jane", C.hCharRoot)
newDoc = SHARED.project.storage.getDocument(cHandle) newDoc = SHARED.project.storage.getDocument(cHandle)
newDoc.writeDocument("# Jane\n\n@tag: Jane\n\n") newDoc.writeDocument("# Jane\n\n@tag: Jane\n\n")
nwGUI.rebuildIndex(beQuiet=True) nwGUI.rebuildIndex()
assert SHARED.focusMode is False assert SHARED.focusMode is False
@@ -825,11 +826,11 @@ def testGuiMain_OpenClose(qtbot, monkeypatch, nwGUI, projPath, fncPath, mockRnd)
nwGUI.viewDocument(C.hTitlePage) nwGUI.viewDocument(C.hTitlePage)
# Handle broken index on project open # Handle broken index on project open
idxData = jsonEncode({"novelWriter.meta": {"version": __hexversion__}})
nwGUI.closeProject() nwGUI.closeProject()
idxPath: Path = projPath / "meta" / nwFiles.INDEX_FILE idxPath: Path = projPath / "meta" / nwFiles.INDEX_FILE
assert idxPath.read_text(encoding="utf-8") != "{}" assert idxPath.read_text(encoding="utf-8") != "{}"
idxPath.write_text("{}", encoding="utf-8") idxPath.write_text(idxData, encoding="utf-8")
assert idxPath.read_text(encoding="utf-8") == "{}"
nwGUI.openProject(projPath) nwGUI.openProject(projPath)
nwGUI.saveProject() nwGUI.saveProject()
+1 -1
View File
@@ -36,7 +36,7 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
cHandle = SHARED.project.newFile("A Note", C.hCharRoot) cHandle = SHARED.project.newFile("A Note", C.hCharRoot)
newDoc = SHARED.project.storage.getDocument(cHandle) newDoc = SHARED.project.storage.getDocument(cHandle)
newDoc.writeDocument("# A Note\n\n") newDoc.writeDocument("# A Note\n\n")
nwGUI.rebuildIndex(beQuiet=True) nwGUI.rebuildIndex()
status = nwGUI.mainStatus status = nwGUI.mainStatus
+1 -1
View File
@@ -66,7 +66,7 @@ class C:
def cmpFiles( def cmpFiles(
fileOne: str | Path, fileOne: str | Path,
fileTwo: str | Path, fileTwo: str | Path,
ignoreLines: list | None = None, ignoreLines: list[int] | None = None,
ignoreStart: tuple | None = None ignoreStart: tuple | None = None
) -> bool: ) -> bool:
"""Compare two files, with optional line ignore.""" """Compare two files, with optional line ignore."""