Stop showing index rebuild dialog on upgrades (#2537)
This commit is contained in:
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,8 +747,7 @@ 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."))
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Main Dialogs
|
# Main Dialogs
|
||||||
|
|||||||
@@ -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"},
|
||||||
|
|||||||
@@ -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."""
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
@@ -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."""
|
||||||
|
|||||||
Reference in New Issue
Block a user