Merge pull request #114 from vkbo/index_changes

Index and Project Changes
This commit is contained in:
Veronica K. Berglyd Olsen
2019-11-02 13:13:42 +01:00
committed by GitHub
6 changed files with 148 additions and 32 deletions
+1
View File
@@ -22,6 +22,7 @@ class nwFiles():
APP_ICON = "novelWriter.svg"
PROJ_FILE = "nwProject.nwx"
PROJ_COUNT = "projCount.txt"
PROJ_DICT = "wordlist.txt"
SESS_INFO = "sessionInfo.log"
INDEX_FILE = "tagsIndex.json"
+2 -1
View File
@@ -220,7 +220,7 @@ class GuiDocTree(QTreeWidget):
def deleteItem(self, tHandle=None):
"""Delete items from the tree. Note that this does not delete the item from the item tree in
the project object. However, since this is only meta data, there isn't really a need to do
that to save memory. As items not in the tree are not saved to the project file, a loaded
that to save memory. Items not in the tree are not saved to the project file, so a loaded
project will be clean anyway.
"""
@@ -247,6 +247,7 @@ class GuiDocTree(QTreeWidget):
self.clearSelection()
trItemP.setSelected(True)
self.theProject.setProjectChanged(True)
self.theParent.theIndex.deleteHandle(tHandle)
elif nwItemS.itemType == nwItemType.FOLDER:
logger.debug("User requested folder %s deleted" % tHandle)
+5 -1
View File
@@ -307,6 +307,10 @@ class GuiMain(QMainWindow):
if self.theProject.lastViewed is not None:
self.viewDocument(self.theProject.lastViewed)
# Check if we need to rebuild the index
if self.theIndex.indexBroken:
self.rebuildIndex()
return True
def saveProject(self):
@@ -607,7 +611,7 @@ class GuiMain(QMainWindow):
"""
if isinstance(theMessage, list):
popMsg = "<br>".join(theMessage)
popMsg = " ".join(theMessage)
logMsg = theMessage
else:
popMsg = theMessage
+89 -26
View File
@@ -17,8 +17,9 @@ import nw
from os import path
from nw.project.document import NWDoc
from nw.enum import nwItemType, nwItemClass
from nw.enum import nwItemType, nwItemClass, nwItemLayout
from nw.constants import nwFiles
from nw.enum import nwAlert
logger = logging.getLogger(__name__)
@@ -35,6 +36,7 @@ class NWIndex():
NOTE_KEYS = [TAG_KEY]
NOVEL_KEYS = [PLOT_KEY, POV_KEY, CHAR_KEY, WORLD_KEY, TIME_KEY, OBJECT_KEY, CUSTOM_KEY]
VALID_KEYS = [TAG_KEY, PLOT_KEY, POV_KEY, CHAR_KEY, WORLD_KEY, TIME_KEY, OBJECT_KEY, CUSTOM_KEY]
TAG_CLASS = {
CHAR_KEY : [nwItemClass.CHARACTER, 1],
POV_KEY : [nwItemClass.CHARACTER, 2],
@@ -48,24 +50,43 @@ class NWIndex():
def __init__(self, theProject, theParent):
# Internal
self.theProject = theProject
self.theParent = theParent
self.mainConf = self.theParent.mainConf
self.theProject = theProject
self.theParent = theParent
self.mainConf = self.theParent.mainConf
self.indexBroken = False
# Indices
self.tagIndex = {}
self.refIndex = {}
self.novelIndex = {}
self.noteIndex = {}
# Lists
self.novelList = []
return
##
# Public Methods
##
def clearIndex(self):
self.tagIndex = {}
self.refIndex = {}
self.novelIndex = {}
self.noteIndex = {}
return
def deleteHandle(self, tHandle):
for tTag in self.tagIndex:
if self.tagIndex[tTag][1] == tHandle:
self.tagIndex.pop(tTag, None)
self.refIndex.pop(tHandle, None)
self.novelIndex.pop(tHandle, None)
self.noteIndex.pop(tHandle, None)
return
##
@@ -95,6 +116,10 @@ class NWIndex():
self.refIndex = theData["refIndex"]
if "novelIndex" in theData.keys():
self.novelIndex = theData["novelIndex"]
if "noteIndex" in theData.keys():
self.noteIndex = theData["noteIndex"]
self.checkIndex()
return True
@@ -116,6 +141,7 @@ class NWIndex():
"tagIndex" : self.tagIndex,
"refIndex" : self.refIndex,
"novelIndex" : self.novelIndex,
"noteIndex" : self.noteIndex,
}, indent=nIndent))
except Exception as e:
logger.error("Failed to save index file")
@@ -124,6 +150,40 @@ class NWIndex():
return True
def checkIndex(self):
"""Check that the entries in the index are valid and contain the elements it should.
"""
self.indexBroken = False
for tTag in self.tagIndex:
if len(self.tagIndex[tTag]) != 3:
self.indexBroken = True
for tHandle in self.refIndex:
for tEntry in self.refIndex[tHandle]:
if len(tEntry) != 4:
self.indexBroken = True
for tHandle in self.novelIndex:
for tEntry in self.novelIndex[tHandle]:
if len(tEntry) != 4:
self.indexBroken = True
for tHandle in self.noteIndex:
for tEntry in self.noteIndex[tHandle]:
if len(tEntry) != 4:
self.indexBroken = True
if self.indexBroken:
self.clearIndex()
self.theParent.makeAlert(
"The project index loaded from cache contains errors. Triggering Rebuild Index.",
nwAlert.WARN
)
return
##
# Index Building
##
@@ -137,6 +197,7 @@ class NWIndex():
theItem = self.theProject.getItem(tHandle)
if theItem is None: return False
if theItem.itemType != nwItemType.FILE: return False
if theItem.parHandle == self.theProject.trashRoot: return False
itemClass = theItem.itemClass
itemLayout = theItem.itemLayout
@@ -148,6 +209,8 @@ class NWIndex():
self.refIndex[tHandle] = []
isNovel = True
else:
self.noteIndex[tHandle] = []
self.refIndex[tHandle] = []
isNovel = False
# Also clear references to file in tag index
@@ -166,19 +229,16 @@ class NWIndex():
nChar = len(aLine)
if nChar == 0: continue
if aLine[0] == "#":
if isNovel:
isTitle = self.indexTitle(tHandle, aLine, nLine, itemLayout)
if isTitle:
nTitle = nLine
isTitle = self.indexTitle(tHandle, isNovel, aLine, nLine, itemLayout)
if isTitle:
nTitle = nLine
elif aLine[0] == "@":
if isNovel:
self.indexNoteRef(tHandle, aLine, nLine, nTitle)
else:
self.indexTag(tHandle, aLine, nLine, itemClass)
self.indexNoteRef(tHandle, aLine, nLine, nTitle)
self.indexTag(tHandle, aLine, nLine, itemClass)
return True
def indexTitle(self, tHandle, aLine, nLine, itemLayout):
def indexTitle(self, tHandle, isNovel, aLine, nLine, itemLayout):
"""Save information about the title and its location in the file.
"""
@@ -198,7 +258,12 @@ class NWIndex():
return False
if hText != "":
self.novelIndex[tHandle].append([nLine, hDepth, hText, itemLayout.name])
if isNovel:
if tHandle in self.novelIndex:
self.novelIndex[tHandle].append([nLine, hDepth, hText, itemLayout.name])
else:
if tHandle in self.noteIndex:
self.noteIndex[tHandle].append([nLine, hDepth, hText, itemLayout.name])
return True
@@ -284,6 +349,11 @@ class NWIndex():
if nBits == 0:
return []
# Check that the key is valid
isGood[0] = theBits[0] in self.VALID_KEYS
if not isGood[0] or nBits == 1:
return isGood
# If we have a tag, only the first value is accepted, the rest is ignored
if theBits[0] == self.TAG_KEY and nBits > 1:
isGood[0] = True
@@ -297,13 +367,6 @@ class NWIndex():
return isGood
# If we're still here, we better check that the references exist
if tItem.itemClass == nwItemClass.NOVEL:
isGood[0] = theBits[0] in self.NOVEL_KEYS
else:
isGood[0] = theBits[0] in self.NOTE_KEYS
if not isGood[0] or nBits == 1:
return isGood
for n in range(1,nBits):
if theBits[n] in self.tagIndex:
isGood[n] = self.TAG_CLASS[theBits[0]][0].name == self.tagIndex[theBits[n]][2]
@@ -329,19 +392,19 @@ class NWIndex():
return True
def buildReferenceList(self, theHandle):
"""Build a list of files referring back to our file, specified by theHandle.
def buildReferenceList(self, tHandle):
"""Build a list of files referring back to our file, specified by tHandle.
"""
theRefs = {}
tItem = self.theProject.getItem(theHandle)
if theHandle is None:
tItem = self.theProject.getItem(tHandle)
if tHandle is None:
return theRefs
theTag = None
for tTag in self.tagIndex:
if theHandle == self.tagIndex[tTag][1]:
if tHandle == self.tagIndex[tTag][1]:
theTag = tTag
break
+50 -3
View File
@@ -14,6 +14,7 @@ import logging
import nw
from os import path, mkdir, listdir
from shutil import copyfile
from lxml import etree
from hashlib import sha256
from datetime import datetime
@@ -196,10 +197,16 @@ class NWProject():
if not self._checkFolder(self.projMeta): return
if not self._checkFolder(self.projCache): return
nwXML = etree.parse(fileName)
xRoot = nwXML.getroot()
try:
nwXML = etree.parse(fileName)
except Exception as e:
self.makeAlert(["Failed to parse project xml.",str(e)], nwAlert.ERROR)
self.clearProject()
return False
xRoot = nwXML.getroot()
nwxRoot = xRoot.tag
nwxRoot = xRoot.tag
appVersion = xRoot.attrib["appVersion"]
fileVersion = xRoot.attrib["fileVersion"]
@@ -288,6 +295,9 @@ class NWProject():
logger.debug("Saving project: %s" % self.projPath)
# Save a copy of the current file, just in case
self._maintainPrevious()
# Root element and project details
logger.debug("Writing project meta")
nwXML = etree.Element("novelWriterXML",attrib={
@@ -699,4 +709,41 @@ class NWProject():
itemHandle = self._makeHandle(addSeed+"!")
return itemHandle
def _maintainPrevious(self):
"""This function will take the current project file and copy it into the project cache
folder with an incremental file extension added. These serve as a backup in case the xml
file gets corrupted.
"""
countFile = path.join(self.projCache, nwFiles.PROJ_COUNT)
projCount = 0
if path.isfile(countFile):
try:
with open(countFile, mode="r") as inFile:
projCount = int(inFile.read())+1
except:
projCount = 0
if projCount > 9:
projCount = 0
projBackup = "%s.%d" % (nwFiles.PROJ_FILE, projCount)
try:
copyfile(
path.join(self.projPath,self.projFile),
path.join(self.projCache,projBackup)
)
except:
logger.error("Failed to write to file %s" % projBackup)
try:
with open(countFile, mode="w") as outFile:
outFile.write(str(projCount))
except:
logger.error("Failed to write to file %s" % countFile)
return
# END Class NWProject
+1 -1
View File
@@ -1 +1 @@
{"tagIndex": {"Jane": [3, "2fca346db6561", "CHARACTER"], "MainPlot": [3, "02d20bbd7e394", "PLOT"], "Home": [3, "7688b6ef52555", "WORLD"]}, "refIndex": {"31489056e0916": [[5, "@pov", "Jane", 3], [6, "@plot", "MainPlot", 3], [11, "@pov", "Jane", 8], [12, "@plot", "MainPlot", 8], [13, "@location", "Home", 8], [17, "@char", "Jane", 15]]}, "novelIndex": {"31489056e0916": [[1, 1, "Novel", "SCENE"], [3, 2, "Chapter", "SCENE"], [8, 3, "Scene", "SCENE"], [15, 4, "Some Section", "SCENE"]]}}
{"tagIndex": {"Jane": [3, "2fca346db6561", "CHARACTER"], "MainPlot": [3, "02d20bbd7e394", "PLOT"], "Home": [3, "7688b6ef52555", "WORLD"]}, "refIndex": {"31489056e0916": [[5, "@pov", "Jane", 3], [6, "@plot", "MainPlot", 3], [11, "@pov", "Jane", 8], [12, "@plot", "MainPlot", 8], [13, "@location", "Home", 8], [17, "@char", "Jane", 15]], "2fca346db6561": [], "02d20bbd7e394": [], "7688b6ef52555": []}, "novelIndex": {"31489056e0916": [[1, 1, "Novel", "SCENE"], [3, 2, "Chapter", "SCENE"], [8, 3, "Scene", "SCENE"], [15, 4, "Some Section", "SCENE"]]}, "noteIndex": {"2fca346db6561": [[1, 1, "Jane Doe", "NOTE"]], "02d20bbd7e394": [[1, 1, "Main Plot", "NOTE"]], "7688b6ef52555": [[1, 1, "Main Location", "NOTE"]]}}