Rewritten index storage using classes instead
This commit is contained in:
+225
-1
@@ -35,7 +35,7 @@ from novelwriter.error import logException
|
|||||||
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode
|
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode
|
||||||
from novelwriter.core.document import NWDoc
|
from novelwriter.core.document import NWDoc
|
||||||
from novelwriter.common import (
|
from novelwriter.common import (
|
||||||
isHandle, isTitleTag, isItemClass, isItemLayout, jsonEncode
|
checkInt, isHandle, isTitleTag, isItemClass, isItemLayout, jsonEncode
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -59,6 +59,9 @@ class NWIndex():
|
|||||||
self._fileIndex = {}
|
self._fileIndex = {}
|
||||||
self._fileMeta = {}
|
self._fileMeta = {}
|
||||||
|
|
||||||
|
self._tags = {}
|
||||||
|
self._items = {}
|
||||||
|
|
||||||
# TimeStamps
|
# TimeStamps
|
||||||
self._timeNovel = 0
|
self._timeNovel = 0
|
||||||
self._timeNotes = 0
|
self._timeNotes = 0
|
||||||
@@ -84,6 +87,10 @@ class NWIndex():
|
|||||||
self._timeNovel = 0
|
self._timeNovel = 0
|
||||||
self._timeNotes = 0
|
self._timeNotes = 0
|
||||||
self._timeIndex = 0
|
self._timeIndex = 0
|
||||||
|
|
||||||
|
self._tags = {}
|
||||||
|
self._items = {}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def deleteHandle(self, tHandle):
|
def deleteHandle(self, tHandle):
|
||||||
@@ -194,6 +201,18 @@ class NWIndex():
|
|||||||
|
|
||||||
logger.verbose("Index saved in %.3f ms", (time() - tStart)*1000)
|
logger.verbose("Index saved in %.3f ms", (time() - tStart)*1000)
|
||||||
|
|
||||||
|
indexFile = os.path.join(self.theProject.projMeta, "tagsIndex2.json")
|
||||||
|
tStart = time()
|
||||||
|
|
||||||
|
itemsIndex = {}
|
||||||
|
for item in self._items.values():
|
||||||
|
item.packData(itemsIndex)
|
||||||
|
|
||||||
|
with open(indexFile, mode="w+", encoding="utf-8") as outFile:
|
||||||
|
outFile.write(jsonEncode(itemsIndex, nmax=3))
|
||||||
|
|
||||||
|
logger.verbose("Index saved in %.3f ms", (time() - tStart)*1000)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -219,6 +238,11 @@ class NWIndex():
|
|||||||
cC, wC, pC = countWords(theText)
|
cC, wC, pC = countWords(theText)
|
||||||
self._fileMeta[tHandle] = ["H0", cC, wC, pC]
|
self._fileMeta[tHandle] = ["H0", cC, wC, pC]
|
||||||
|
|
||||||
|
self._items[tHandle] = IndexItem(tHandle, theItem)
|
||||||
|
theItem.setCharCount(cC)
|
||||||
|
theItem.setWordCount(wC)
|
||||||
|
theItem.setParaCount(pC)
|
||||||
|
|
||||||
# If the file's meta data is missing, or the file is out of the
|
# If the file's meta data is missing, or the file is out of the
|
||||||
# main project, we don't index the content
|
# main project, we don't index the content
|
||||||
if theItem.itemLayout == nwItemLayout.NO_LAYOUT:
|
if theItem.itemLayout == nwItemLayout.NO_LAYOUT:
|
||||||
@@ -338,6 +362,10 @@ class NWIndex():
|
|||||||
# first header level is recorded in the file meta index
|
# first header level is recorded in the file meta index
|
||||||
self._fileMeta[tHandle][0] = hDepth
|
self._fileMeta[tHandle][0] = hDepth
|
||||||
|
|
||||||
|
tItem = self._items[tHandle]
|
||||||
|
tItem.updateLevel(hDepth)
|
||||||
|
tItem.addHeading(IndexHeading(sTitle, hDepth, hText))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _indexPage(self, tHandle, itemLayout):
|
def _indexPage(self, tHandle, itemLayout):
|
||||||
@@ -364,6 +392,8 @@ class NWIndex():
|
|||||||
self._fileIndex[tHandle][sTitle]["cCount"] = cC
|
self._fileIndex[tHandle][sTitle]["cCount"] = cC
|
||||||
self._fileIndex[tHandle][sTitle]["wCount"] = wC
|
self._fileIndex[tHandle][sTitle]["wCount"] = wC
|
||||||
self._fileIndex[tHandle][sTitle]["pCount"] = pC
|
self._fileIndex[tHandle][sTitle]["pCount"] = pC
|
||||||
|
if tHandle in self._items:
|
||||||
|
self._items[tHandle].setHeadingCounts(sTitle, cC, wC, pC)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _indexSynopsis(self, tHandle, theText, nTitle):
|
def _indexSynopsis(self, tHandle, theText, nTitle):
|
||||||
@@ -373,6 +403,8 @@ class NWIndex():
|
|||||||
if tHandle in self._fileIndex:
|
if tHandle in self._fileIndex:
|
||||||
if sTitle in self._fileIndex[tHandle]:
|
if sTitle in self._fileIndex[tHandle]:
|
||||||
self._fileIndex[tHandle][sTitle]["synopsis"] = theText
|
self._fileIndex[tHandle][sTitle]["synopsis"] = theText
|
||||||
|
if tHandle in self._items:
|
||||||
|
self._items[tHandle].setHeadingSynopsis(sTitle, theText)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _indexKeyword(self, tHandle, aLine, nLine, nTitle, itemClass):
|
def _indexKeyword(self, tHandle, aLine, nLine, nTitle, itemClass):
|
||||||
@@ -391,6 +423,8 @@ class NWIndex():
|
|||||||
sTitle = f"T{nTitle:06d}"
|
sTitle = f"T{nTitle:06d}"
|
||||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||||
self._tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name, sTitle]
|
self._tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name, sTitle]
|
||||||
|
if tHandle in self._items:
|
||||||
|
self._items[tHandle].setHeadingTag(sTitle, theBits[1])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if tHandle not in self._refIndex:
|
if tHandle not in self._refIndex:
|
||||||
@@ -399,6 +433,8 @@ class NWIndex():
|
|||||||
self._refIndex[tHandle][sTitle] = []
|
self._refIndex[tHandle][sTitle] = []
|
||||||
for aVal in theBits[1:]:
|
for aVal in theBits[1:]:
|
||||||
self._refIndex[tHandle][sTitle].append([nLine, theBits[0], aVal])
|
self._refIndex[tHandle][sTitle].append([nLine, theBits[0], aVal])
|
||||||
|
if tHandle in self._items:
|
||||||
|
self._items[tHandle].addHeadingReferences(sTitle, theBits[1:], theBits[0])
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -892,3 +928,191 @@ def countWords(theText):
|
|||||||
prevEmpty = not countPara
|
prevEmpty = not countPara
|
||||||
|
|
||||||
return charCount, wordCount, paraCount
|
return charCount, wordCount, paraCount
|
||||||
|
|
||||||
|
|
||||||
|
class IndexItem:
|
||||||
|
|
||||||
|
DEF_HKEY = "T000000"
|
||||||
|
|
||||||
|
def __init__(self, tHandle, tItem):
|
||||||
|
self._handle = tHandle
|
||||||
|
self._item = tItem
|
||||||
|
|
||||||
|
self._level = "H0"
|
||||||
|
self._headings = {}
|
||||||
|
|
||||||
|
# Add a placeholder heading
|
||||||
|
self._headings[self.DEF_HKEY] = IndexHeading(self.DEF_HKEY)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Properties
|
||||||
|
##
|
||||||
|
|
||||||
|
@property
|
||||||
|
def level(self):
|
||||||
|
return self._level
|
||||||
|
|
||||||
|
##
|
||||||
|
# Setters
|
||||||
|
##
|
||||||
|
|
||||||
|
def setLevel(self, level):
|
||||||
|
if level in H_VALID:
|
||||||
|
self._level = level
|
||||||
|
else:
|
||||||
|
self._level = "H0"
|
||||||
|
return
|
||||||
|
|
||||||
|
def updateLevel(self, level):
|
||||||
|
"""Set the level only if it is H0.
|
||||||
|
"""
|
||||||
|
if level in H_VALID and self._level == "H0":
|
||||||
|
self._level = level
|
||||||
|
else:
|
||||||
|
self._level = "H0"
|
||||||
|
return
|
||||||
|
|
||||||
|
def addHeading(self, tHeading):
|
||||||
|
if "T000000" in self._headings:
|
||||||
|
self._headings.pop("T000000")
|
||||||
|
self._headings[tHeading.key] = tHeading
|
||||||
|
return
|
||||||
|
|
||||||
|
def setHeadingCounts(self, sTitle, charCount, wordCount, paraCount):
|
||||||
|
if sTitle in self._headings:
|
||||||
|
self._headings[sTitle].setCounts(charCount, wordCount, paraCount)
|
||||||
|
return
|
||||||
|
|
||||||
|
def setHeadingSynopsis(self, sTitle, synopText):
|
||||||
|
if sTitle in self._headings:
|
||||||
|
self._headings[sTitle].setSynopsis(synopText)
|
||||||
|
return
|
||||||
|
|
||||||
|
def setHeadingTag(self, sTitle, tagKey):
|
||||||
|
if sTitle in self._headings:
|
||||||
|
self._headings[sTitle].setTag(tagKey)
|
||||||
|
return
|
||||||
|
|
||||||
|
def addHeadingReferences(self, sTitle, tagKeys, refType):
|
||||||
|
if sTitle in self._headings:
|
||||||
|
for tagKey in tagKeys:
|
||||||
|
self._headings[sTitle].addReference(tagKey, refType)
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Data Methods
|
||||||
|
##
|
||||||
|
|
||||||
|
def packData(self, container):
|
||||||
|
"""Pack the indexed item's data into an existing dictionary.
|
||||||
|
"""
|
||||||
|
container[self._handle] = {
|
||||||
|
"firstLevel": self._level,
|
||||||
|
}
|
||||||
|
container[self._handle]["headings"] = {
|
||||||
|
key: value.packData() for key, value in self._headings.items()
|
||||||
|
}
|
||||||
|
container[self._handle]["references"] = {
|
||||||
|
key: value.packReferences() for key, value in self._headings.items()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
# END Class IndexItem
|
||||||
|
|
||||||
|
|
||||||
|
class IndexHeading:
|
||||||
|
|
||||||
|
def __init__(self, key, level="H0", title=""):
|
||||||
|
self._key = key
|
||||||
|
self._level = level
|
||||||
|
self._title = title
|
||||||
|
|
||||||
|
self._charCount = 0
|
||||||
|
self._wordCount = 0
|
||||||
|
self._paraCount = 0
|
||||||
|
self._synopsis = ""
|
||||||
|
|
||||||
|
self._tag = ""
|
||||||
|
self._refs = {}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Properties
|
||||||
|
##
|
||||||
|
|
||||||
|
@property
|
||||||
|
def key(self):
|
||||||
|
return self._key
|
||||||
|
|
||||||
|
##
|
||||||
|
# Setters
|
||||||
|
##
|
||||||
|
|
||||||
|
def setLevel(self, level):
|
||||||
|
if level in H_VALID:
|
||||||
|
self._level = level
|
||||||
|
else:
|
||||||
|
self._level = "H0"
|
||||||
|
return
|
||||||
|
|
||||||
|
def setCounts(self, charCount, wordCount, paraCount):
|
||||||
|
self._charCount = max(0, checkInt(charCount, 0))
|
||||||
|
self._wordCount = max(0, checkInt(wordCount, 0))
|
||||||
|
self._paraCount = max(0, checkInt(paraCount, 0))
|
||||||
|
return
|
||||||
|
|
||||||
|
def setSynopsis(self, synopText):
|
||||||
|
self._synopsis = str(synopText)
|
||||||
|
return
|
||||||
|
|
||||||
|
def setTag(self, tagKey):
|
||||||
|
self._tag = str(tagKey)
|
||||||
|
return
|
||||||
|
|
||||||
|
def addReference(self, tagKey, refType):
|
||||||
|
"""Add a record of a reference tag, and what keyword types it is
|
||||||
|
associated with.
|
||||||
|
"""
|
||||||
|
if tagKey not in self._refs:
|
||||||
|
self._refs[tagKey] = set()
|
||||||
|
self._refs[tagKey].add(refType)
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Data Methods
|
||||||
|
##
|
||||||
|
|
||||||
|
def packData(self):
|
||||||
|
"""Pack the values into a dictionary for saving to cache.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"level": self._level,
|
||||||
|
"title": self._title,
|
||||||
|
"tag": self._tag,
|
||||||
|
"cCount": self._charCount,
|
||||||
|
"wCount": self._wordCount,
|
||||||
|
"pCount": self._paraCount,
|
||||||
|
"synopsis": self._synopsis,
|
||||||
|
}
|
||||||
|
|
||||||
|
def packReferences(self):
|
||||||
|
return {key: list(value) for key, value in self._refs.items()}
|
||||||
|
|
||||||
|
def unpackData(self, data):
|
||||||
|
"""Unpack a title entry
|
||||||
|
"""
|
||||||
|
self._setLevel(data.get("level", "H0"))
|
||||||
|
self._title = str(data.get("title", ""))
|
||||||
|
self._tag = str(data.get("tag", ""))
|
||||||
|
self.setCounts(
|
||||||
|
data.get("cCount", 0),
|
||||||
|
data.get("wCount", 0),
|
||||||
|
data.get("pCount", 0),
|
||||||
|
)
|
||||||
|
self._synopsis = str(data.get("synopsis", ""))
|
||||||
|
return
|
||||||
|
|
||||||
|
# END Class IndexHeading
|
||||||
|
|||||||
Reference in New Issue
Block a user