From 36ca2ee309b4fff304e3d581e925a0e63547dcd7 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 29 May 2020 23:01:25 +0200 Subject: [PATCH 1/8] Cleane dup project open and handling of legacy project content --- nw/constants/constants.py | 2 + nw/core/__init__.py | 2 - nw/core/project.py | 209 ++++++++++++++++++++++--------- nw/core/tools.py | 40 ------ sample/content/ae7339df26ded.nwd | 2 +- sample/content/b8136a5a774a0.nwd | 2 +- sample/content/edca4be2fcaf8.nwd | 2 +- sample/content/f1471bef9f2ae.nwd | 6 - sample/nwProject.nwx | 34 ++--- 9 files changed, 172 insertions(+), 127 deletions(-) diff --git a/nw/constants/constants.py b/nw/constants/constants.py index bc20af31..cf0ab4eb 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -39,6 +39,8 @@ class nwFiles(): PROJ_FILE = "nwProject.nwx" PROJ_DICT = "wordlist.txt" PROJ_LOCK = "nwProject.lock" + TOC_TXT = "ToC.txt" + TOC_JSON = "ToC.json" SESS_INFO = "sessionInfo.log" INDEX_FILE = "tagsIndex.json" OPTS_FILE = "guiOptions.json" diff --git a/nw/core/__init__.py b/nw/core/__init__.py index 6dd65fd2..0107dac2 100644 --- a/nw/core/__init__.py +++ b/nw/core/__init__.py @@ -9,7 +9,6 @@ from nw.core.spellcheck import NWSpellSimple from nw.core.tokenizer import Tokenizer from nw.core.tohtml import ToHtml from nw.core.tools import countWords -from nw.core.tools import projectMaintenance from nw.core.tools import numberToWord __all__ = [ @@ -22,6 +21,5 @@ __all__ = [ "Tokenizer", "ToHtml", "countWords", - "projectMaintenance", "numberToWord", ] diff --git a/nw/core/project.py b/nw/core/project.py index cf35f629..c89bb645 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -42,7 +42,6 @@ from shutil import make_archive from PyQt5.QtWidgets import QMessageBox from nw.gui.tools import OptionState -from nw.core.tools import projectMaintenance from nw.core.document import NWDoc from nw.common import checkString, checkBool, checkInt, formatTimeStamp from nw.constants import ( @@ -72,11 +71,12 @@ class NWProject(): self.autoCount = 0 # Meta data: number of automatic saves # Class Settings - self.projPath = None # The full path to where the currently open project is saved - self.projMeta = None # The full path to the project's meta data folder - self.projData = None # The full path to the project's data folder - self.projDict = None # The spell check dictionary - self.projFile = None # The file name of the project main XML file + self.projPath = None # The full path to where the currently open project is saved + self.projMeta = None # The full path to the project's meta data folder + self.projCache = None # The full path to the project's cache folder + self.projContent = None # The full path to the project's content folder + self.projDict = None # The spell check dictionary + self.projFile = None # The file name of the project main XML file # Project Meta self.projName = "" # Project name (working title) @@ -196,7 +196,8 @@ class NWProject(): # Project Settings self.projPath = None self.projMeta = None - self.projData = None + self.projCache = None + self.projContent = None self.projDict = None self.projFile = nwFiles.PROJ_FILE self.projName = "" @@ -248,14 +249,37 @@ class NWProject(): self.projPath = path.abspath(path.dirname(fileName)) logger.debug("Opening project: %s" % self.projPath) - self.projMeta = path.join(self.projPath,"meta") - self.projData = path.join(self.projPath,"content") - self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT) + # Standard Folders and Files + # ========================== + + self.projMeta = path.join(self.projPath, "meta") + self.projCache = path.join(self.projPath, "cache") + self.projContent = path.join(self.projPath, "content") + self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT) if not self._checkFolder(self.projMeta): return False - if not self._checkFolder(self.projData): + if not self._checkFolder(self.projCache): return False + if not self._checkFolder(self.projContent): + return False + + # Check for Old Legacy Data + # ========================= + + errList = [] + for projItem in listdir(self.projPath): + logger.verbose("Project contains: %s" % projItem) + if projItem.startswith("data_"): + self._legacyDataFolder(projItem) + + if errList: + self.makeAlert(errList, nwAlert.ERROR) + + self._deprecatedFiles() + + # Project Lock + # ============ if overrideLock: self._clearLockFile() @@ -272,10 +296,8 @@ class NWProject(): else: logger.verbose("Project is not locked") - try: - projectMaintenance(self) - except Exception as E: - logger.error(str(E)) + # Open The Project XML File + # ========================= try: nwXML = etree.parse(fileName) @@ -321,6 +343,7 @@ class NWProject(): # Check File Type # =============== + if not nwxRoot == "novelWriterXML": self.makeAlert( "Project file does not appear to be a novelWriterXML file.", @@ -330,14 +353,17 @@ class NWProject(): # Check Project Storage Version # ============================= + if fileVersion == "1.0": msgBox = QMessageBox() msgRes = msgBox.question(self.theParent, "Old Project Version", ( "The project file and data is created by a %s version lower than 0.7. " "Do you want to upgrade the project to the most recent format?

" "Note that after the upgrade, you cannot open the project with an older " - "version of novelWriter any more, so make sure you have a recent backup." - ) % nw.__package__) + "version of %s any more, so make sure you have a recent backup." + ) % ( + nw.__package__, nw.__package__ + )) if msgRes == QMessageBox.Yes: self._updateStorage() else: @@ -353,6 +379,7 @@ class NWProject(): # Check novelWriter Version # ========================= + if int(hexVersion, 16) > int(nw.__hexversion__, 16) and self.mainConf.showGUI: msgBox = QMessageBox() msgRes = msgBox.question(self.theParent, "Version Conflict", ( @@ -367,6 +394,7 @@ class NWProject(): # Start Parsing XML # ================= + for xChild in xRoot: if xChild.tag == "project": logger.debug("Found project meta") @@ -384,6 +412,7 @@ class NWProject(): self.bookAuthors.append(xItem.text) elif xItem.tag == "backup": self.doBackup = checkBool(xItem.text, False) + elif xChild.tag == "settings": logger.debug("Found project settings") for xItem in xChild: @@ -411,6 +440,7 @@ class NWProject(): for xEntry in xItem: titleFormat[xEntry.tag] = checkString(xEntry.text, "", False) self.setTitleFormat(titleFormat) + elif xChild.tag == "content": logger.debug("Found project content") self.projTree.unpackXML(xChild) @@ -444,14 +474,14 @@ class NWProject(): return False self.projMeta = path.join(self.projPath, "meta") - self.projData = path.join(self.projPath, "content") + self.projContent = path.join(self.projPath, "content") saveTime = time() if not self._checkFolder(self.projPath): return False if not self._checkFolder(self.projMeta): return False - if not self._checkFolder(self.projData): + if not self._checkFolder(self.projContent): return False logger.debug("Saving project: %s" % self.projPath) @@ -978,7 +1008,7 @@ class NWProject(): # Then check the files in the data folder orphanFiles = [] - for fileItem in listdir(self.projData): + for fileItem in listdir(self.projContent): if not fileItem.endswith(".nwd"): logger.warning("Skipping file %s" % fileItem) continue @@ -1046,52 +1076,113 @@ class NWProject(): return True - def _updateStorage(self): - """Updates the project storage folder from 1.0 to 1.1. + ## + # Legacy Data Structure Handlers + ## + + def _legacyDataFolder(self, theFolder): + """Clean up legacy data folders. """ - contDir = path.join(self.projPath, "content") - self._checkFolder(contDir) errList = [] + theData = path.join(self.projPath, theFolder) + if not path.isdir(theData): + errList.append("Not a folder: %s" % theData) + return errList - for projItem in listdir(self.projPath): - itemPath = path.join(self.projPath, projItem) - if not path.isdir(itemPath) or not projItem.startswith("data_"): + logger.info("Old data folder %s found" % theFolder) + + # Move Documents to Content + # ========================= + for dataItem in listdir(theData): + theFile = path.join(theData, dataItem) + if not path.isfile(theFile): + theErr = self._moveUnknownItem(theData, dataItem) + if theErr: + errList.append(theErr) continue - for dataFile in listdir(itemPath): - dataPath = path.join(itemPath, dataFile) - if dataFile.endswith(".bak"): - try: - unlink(dataPath) - logger.info("Deleted file: %s" % dataPath) - except: - errList.append("Failed to delete: %s" % dataPath) - elif dataFile.endswith(".nwd") and len(dataFile) == 21: - tHandle = projItem[-1]+dataFile[:12] - newPath = path.join(contDir, tHandle+".nwd") - try: - rename(dataPath, newPath) - logger.info("Moved file: %s" % dataPath) - logger.info("New location: %s" % newPath) - except: - errList.append("Failed to move: %s" % dataPath) + if len(dataItem) == 21 and dataItem.endswith("_main.nwd"): + tHandle = theFolder[-1]+dataItem[:12] + newPath = path.join(self.projContent, tHandle+".nwd") + try: + rename(theFile, newPath) + logger.info("Moved file: %s" % theFile) + logger.info("New location: %s" % newPath) + except Exception as e: + logger.error(str(e)) + errList.append("Could not move: %s" % theFile) - else: - newPath = path.join(self.projPath, "unknown_"+dataFile) - try: - rename(dataPath, newPath) - logger.info("Moved file: %s" % dataPath) - logger.info("New location: %s" % newPath) - except: - errList.append("Failed to move: %s" % dataPath) - try: - rmdir(itemPath) - logger.info("Removed folder: %s" % itemPath) - except: - errList.append("Failed to delete: %s" % itemPath) + elif len(dataItem) == 21 and dataItem.endswith("_main.bak"): + try: + unlink(theFile) + logger.info("Deleted file: %s" % theFile) + except Exception as e: + logger.error(str(e)) + errList.append("Could not delete: %s" % theFile) - if errList: - self.makeAlert(errList, nwAlert.ERROR) + else: + theErr = self._moveUnknownItem(theData, dataItem) + if theErr: + errList.append(theErr) + + # Remove Data Folder + # ================== + try: + rmdir(theData) + logger.info("Removed folder: %s" % theFolder) + except: + errList.append("Failed to remove: %s" % theFolder) + + return errList + + def _moveUnknownItem(self, theDir, theItem): + """Move an item that doesn't belong in the project folder to + a junk folder. + """ + theJunk = path.join(self.projPath, "junk") + if not self._checkFolder(theJunk): + return "Could not make folder: %s" % theJunk + + theSrc = path.join(theDir, theItem) + theDst = path.join(theJunk, theItem) + + try: + rename(theSrc, theDst) + logger.info("Moved to junk: %s" % theSrc) + except Exception as e: + logger.error(str(e)) + return "Could not move item %s to junk." % theSrc + + return "" + + def _deprecatedFiles(self): + """Delete files that are no longer used by novelWriter. + """ + rmList = [] + rmList.append(path.join(self.projCache, "nwProject.nwx.0")) + rmList.append(path.join(self.projCache, "nwProject.nwx.1")) + rmList.append(path.join(self.projCache, "nwProject.nwx.2")) + rmList.append(path.join(self.projCache, "nwProject.nwx.3")) + rmList.append(path.join(self.projCache, "nwProject.nwx.4")) + rmList.append(path.join(self.projCache, "nwProject.nwx.5")) + rmList.append(path.join(self.projCache, "nwProject.nwx.6")) + rmList.append(path.join(self.projCache, "nwProject.nwx.7")) + rmList.append(path.join(self.projCache, "nwProject.nwx.8")) + rmList.append(path.join(self.projCache, "nwProject.nwx.9")) + rmList.append(path.join(self.projMeta, "mainOptions.json")) + rmList.append(path.join(self.projMeta, "exportOptions.json")) + rmList.append(path.join(self.projMeta, "outlineOptions.json")) + rmList.append(path.join(self.projMeta, "timelineOptions.json")) + rmList.append(path.join(self.projMeta, "docMergeOptions.json")) + rmList.append(path.join(self.projMeta, "sessionLogOptions.json")) + + for rmFile in rmList: + if path.isfile(rmFile): + logger.info("Deleting: %s" % rmFile) + try: + unlink(rmFile) + except Exception as e: + logger.error(str(e)) return diff --git a/nw/core/tools.py b/nw/core/tools.py index 8aa81d7b..8115105d 100644 --- a/nw/core/tools.py +++ b/nw/core/tools.py @@ -8,7 +8,6 @@ File History: Created: 2019-04-22 [0.0.1] countWords Created: 2019-10-13 [0.2.3] numberToWord, _numberToWordEN - Created: 2020-02-13 [0.4.3] projectMaintenance Merged: 2020-05-08 [0.4.5] All of the above into this file This file is a part of novelWriter @@ -81,45 +80,6 @@ def countWords(theText): return charCount, wordCount, paraCount -def projectMaintenance(theProject): - """Wrapper class for handling various tasks related to managing old - projects with content from older versions of novelWriter. - """ - # Remove no longer used project cache folder - if path.isdir(theProject.projPath): - cacheDir = path.join(theProject.projPath, "cache") - if path.isdir(cacheDir): - logger.info("Deprecated cache folder content found") - rmList = [] - for i in range(10): - rmList.append(path.join(cacheDir, "nwProject.nwx.%d" % i)) - rmList.append(path.join(cacheDir, "projCount.txt")) - for rmFile in rmList: - if path.isfile(rmFile): - logger.info("Deleting: %s" % rmFile) - try: - unlink(rmFile) - except Exception as e: - logger.error(str(e)) - - # Remove no longer used meta files - rmList = [] - rmList.append(path.join(theProject.projMeta, "mainOptions.json")) - rmList.append(path.join(theProject.projMeta, "exportOptions.json")) - rmList.append(path.join(theProject.projMeta, "outlineOptions.json")) - rmList.append(path.join(theProject.projMeta, "timelineOptions.json")) - rmList.append(path.join(theProject.projMeta, "docMergeOptions.json")) - rmList.append(path.join(theProject.projMeta, "sessionLogOptions.json")) - for rmFile in rmList: - if path.isfile(rmFile): - logger.info("Deleting: %s" % rmFile) - try: - unlink(rmFile) - except Exception as e: - logger.error(str(e)) - - return - def numberToWord(numVal, theLanguage): """Wrapper for converting numbers to words for chapter headings. """ diff --git a/sample/content/ae7339df26ded.nwd b/sample/content/ae7339df26ded.nwd index 8b92a69c..05ed60d5 100644 --- a/sample/content/ae7339df26ded.nwd +++ b/sample/content/ae7339df26ded.nwd @@ -2,6 +2,6 @@ ### We Found John! @pov: John -@location: Mars, OuterSpace +@location: Mars Jane has been searching for a while, and she finally found John on Mars. He was indeed in space! What was he doing on Mars anyway? Well, it turns out, he was farming potatoes. diff --git a/sample/content/b8136a5a774a0.nwd b/sample/content/b8136a5a774a0.nwd index a304cb35..4e63373d 100644 --- a/sample/content/b8136a5a774a0.nwd +++ b/sample/content/b8136a5a774a0.nwd @@ -1,4 +1,4 @@ -%%~ b8136a5a774a0:7031beac91f75:Delete Me! +%%~ b8136a5a774a0:98acd8c76c93a:Delete Me! ### Delete Me! This scene is trash. \ No newline at end of file diff --git a/sample/content/edca4be2fcaf8.nwd b/sample/content/edca4be2fcaf8.nwd index 5fef1f34..679e4e27 100644 --- a/sample/content/edca4be2fcaf8.nwd +++ b/sample/content/edca4be2fcaf8.nwd @@ -1,4 +1,4 @@ -%%~ edca4be2fcaf8:7031beac91f75:Part One +%%~ edca4be2fcaf8:7031beac91f75:Part 1 # Part One The first part. \ No newline at end of file diff --git a/sample/content/f1471bef9f2ae.nwd b/sample/content/f1471bef9f2ae.nwd index 3bd03e9d..9b2d9368 100644 --- a/sample/content/f1471bef9f2ae.nwd +++ b/sample/content/f1471bef9f2ae.nwd @@ -5,10 +5,4 @@ Space … it’s an awful lot of nothing, with bits in it here and there. Some of which, people like to call home. -## Outer Space -@tag: OuterSpace - -Now even further into space! - -You can have more than one tag in a file, as long as there is only one tag per heading. diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index b6f63962..58b97959 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -11,8 +11,8 @@ True True 636b6aa9b697b - bc0cbd2a407f3 - 941 + ba8a28a246524 + 914 B E @@ -73,7 +73,7 @@ False True PAGE - 210 + 208 40 2 213 @@ -89,7 +89,7 @@ 23 5 1 - 27 + 0 A Folder @@ -122,7 +122,7 @@ 1199 216 7 - 1066 + 1266 Another Scene @@ -135,7 +135,7 @@ 476 93 3 - 428 + 551 Interlude @@ -148,7 +148,7 @@ 633 101 3 - 752 + 1238 A Note on Structure @@ -161,7 +161,7 @@ 1692 313 6 - 551 + 1721 Chapter Two @@ -174,7 +174,7 @@ 139 28 1 - 242 + 343 We Found John! @@ -214,7 +214,7 @@ 49 9 1 - 65 + 24 Jane Smith @@ -227,7 +227,7 @@ 55 9 1 - 71 + 25 Locations @@ -247,7 +247,7 @@ 76 15 1 - 93 + 20 Space @@ -257,10 +257,10 @@ False True NOTE - 241 - 51 - 3 - 135 + 115 + 24 + 1 + 133 Mars From cae9705e4f0fe7f09e1736ba010aa781f2edf09d Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 29 May 2020 23:03:04 +0200 Subject: [PATCH 2/8] Files don't need the expanded flag saved to XML --- nw/core/project.py | 3 ++- sample/nwProject.nwx | 18 +----------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/nw/core/project.py b/nw/core/project.py index c89bb645..abaed265 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -1572,7 +1572,6 @@ class NWItem(): xSub = self._subPack(xPack,"type", text=str(self.itemType.name)) xSub = self._subPack(xPack,"class", text=str(self.itemClass.name)) xSub = self._subPack(xPack,"status", text=str(self.itemStatus)) - xSub = self._subPack(xPack,"expanded", text=str(self.isExpanded)) if self.itemType == nwItemType.FILE: xSub = self._subPack(xPack,"exported", text=str(self.isExported)) xSub = self._subPack(xPack,"layout", text=str(self.itemLayout.name)) @@ -1580,6 +1579,8 @@ class NWItem(): xSub = self._subPack(xPack,"wordCount", text=str(self.wordCount), none=False) xSub = self._subPack(xPack,"paraCount", text=str(self.paraCount), none=False) xSub = self._subPack(xPack,"cursorPos", text=str(self.cursorPos), none=False) + else: + xSub = self._subPack(xPack,"expanded", text=str(self.isExpanded)) return def unpackXML(self, xItem): diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 58b97959..60752873 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -57,7 +57,6 @@ FILE NOVEL Started - False True TITLE 72 @@ -70,7 +69,6 @@ FILE NOVEL New - False True PAGE 208 @@ -83,7 +81,6 @@ FILE NOVEL New - False True PARTITION 23 @@ -103,7 +100,6 @@ FILE NOVEL Notes - False True CHAPTER 12 @@ -116,7 +112,6 @@ FILE NOVEL 1st Draft - False True SCENE 1199 @@ -129,7 +124,6 @@ FILE NOVEL 1st Draft - False True SCENE 476 @@ -142,7 +136,6 @@ FILE NOVEL Finished - False True UNNUMBERED 633 @@ -155,7 +148,6 @@ FILE NOVEL 2nd Draft - False False NOTE 1692 @@ -168,7 +160,6 @@ FILE NOVEL 1st Draft - False True CHAPTER 139 @@ -181,7 +172,6 @@ FILE NOVEL 1st Draft - False True SCENE 189 @@ -208,7 +198,6 @@ FILE CHARACTER Minor - False True NOTE 49 @@ -221,7 +210,6 @@ FILE CHARACTER Major - False True NOTE 55 @@ -241,7 +229,6 @@ FILE WORLD Main - False True NOTE 76 @@ -254,7 +241,6 @@ FILE WORLD Minor - False True NOTE 115 @@ -267,7 +253,6 @@ FILE WORLD Major - False True NOTE 28 @@ -287,7 +272,6 @@ FILE NOVEL New - False True SCENE 0 From 421c40c288508f551c4875aa3c88b097d52aa280 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 29 May 2020 23:08:27 +0200 Subject: [PATCH 3/8] Fixed tests --- tests/reference/gui/0_nwProject.nwx | 3 +-- tests/reference/gui/1_nwProject.nwx | 6 +----- tests/reference/gui/2_nwProject.nwx | 3 +-- tests/reference/gui/3_nwProject.nwx | 3 +-- tests/reference/proj/1_nwProject.nwx | 3 +-- tests/reference/proj/2_nwProject.nwx | 3 +-- tests/reference/proj/3_nwProject.nwx | 5 +---- 7 files changed, 7 insertions(+), 19 deletions(-) diff --git a/tests/reference/gui/0_nwProject.nwx b/tests/reference/gui/0_nwProject.nwx index 99ea2a3d..6d2bf739 100644 --- a/tests/reference/gui/0_nwProject.nwx +++ b/tests/reference/gui/0_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -55,7 +55,6 @@ FILE NOVEL New - False True SCENE 0 diff --git a/tests/reference/gui/1_nwProject.nwx b/tests/reference/gui/1_nwProject.nwx index c9bb4dad..112a4be0 100644 --- a/tests/reference/gui/1_nwProject.nwx +++ b/tests/reference/gui/1_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -55,7 +55,6 @@ FILE NOVEL New - False True SCENE 331 @@ -75,7 +74,6 @@ FILE CHARACTER New - False True NOTE 34 @@ -95,7 +93,6 @@ FILE PLOT New - False True NOTE 48 @@ -115,7 +112,6 @@ FILE WORLD New - False True NOTE 51 diff --git a/tests/reference/gui/2_nwProject.nwx b/tests/reference/gui/2_nwProject.nwx index bf3c5b61..c4668fe4 100644 --- a/tests/reference/gui/2_nwProject.nwx +++ b/tests/reference/gui/2_nwProject.nwx @@ -1,5 +1,5 @@ - + Project Name Project Title @@ -59,7 +59,6 @@ FILE NOVEL New - False True SCENE 0 diff --git a/tests/reference/gui/3_nwProject.nwx b/tests/reference/gui/3_nwProject.nwx index f7db491f..35e1b8df 100644 --- a/tests/reference/gui/3_nwProject.nwx +++ b/tests/reference/gui/3_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -55,7 +55,6 @@ FILE NOVEL Note - False False PAGE 0 diff --git a/tests/reference/proj/1_nwProject.nwx b/tests/reference/proj/1_nwProject.nwx index 651ae852..53499ddc 100644 --- a/tests/reference/proj/1_nwProject.nwx +++ b/tests/reference/proj/1_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -76,7 +76,6 @@ FILE NOVEL New - False True SCENE 0 diff --git a/tests/reference/proj/2_nwProject.nwx b/tests/reference/proj/2_nwProject.nwx index 7bd8d7cb..22a9e0fb 100644 --- a/tests/reference/proj/2_nwProject.nwx +++ b/tests/reference/proj/2_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -76,7 +76,6 @@ FILE NOVEL New - False True SCENE 0 diff --git a/tests/reference/proj/3_nwProject.nwx b/tests/reference/proj/3_nwProject.nwx index 94f11ba7..ee8f6e1a 100644 --- a/tests/reference/proj/3_nwProject.nwx +++ b/tests/reference/proj/3_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -76,7 +76,6 @@ FILE NOVEL New - False True SCENE 0 @@ -117,7 +116,6 @@ FILE NOVEL New - False True SCENE 0 @@ -130,7 +128,6 @@ FILE CHARACTER New - False True NOTE 0 From 5d2f6d33b5785c606f0d6f5c33e62a79928ef999 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 29 May 2020 23:31:38 +0200 Subject: [PATCH 4/8] Also updated the documen class with new project variables --- .gitignore | 10 ++++------ nw/core/document.py | 41 +++++++++++++++++++---------------------- nw/core/project.py | 44 +++++++++++++++++++++++++------------------- sample/nwProject.nwx | 6 +++--- 4 files changed, 51 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 2905634a..c87816d0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,12 +14,10 @@ docs/source/_* __pycache__ # Sample Project -sample/**/cache -sample/**/wordlist.txt -sample/**/sessionInfo.log -sample/**/*.bak -sample/**/*.json -sample/**/*.lock +sample/cache +sample/meta +sample/*.bak +sample/*.lock # PyTest tests/temp diff --git a/nw/core/document.py b/nw/core/document.py index 7ce4de4e..6ab4d500 100644 --- a/nw/core/document.py +++ b/nw/core/document.py @@ -91,18 +91,17 @@ class NWDoc(): if self.theItem.parHandle == self.theProject.projTree.trashRoot(): self.docEditable = False - docDir = "content" docFile = self.docHandle+".nwd" - self.fileLoc = path.join(docDir, docFile) - logger.debug("Opening document %s" % self.fileLoc) - dataDir = path.join(self.theProject.projPath, docDir) - docPath = path.join(dataDir, docFile) + logger.debug("Opening document %s" % docFile) + + docPath = path.join(self.theProject.projContent, docFile) + self.fileLoc = docPath theText = "" self.docMeta = "" if path.isfile(docPath): try: - with open(docPath,mode="r",encoding="utf8") as inFile: + with open(docPath, mode="r", encoding="utf8") as inFile: fstLine = inFile.readline() if fstLine.startswith("%%~ "): # This is the meta line @@ -112,7 +111,7 @@ class NWDoc(): theText += inFile.read() except Exception as e: - self.makeAlert(["Failed to open document file.",str(e)], nwAlert.ERROR) + self.makeAlert(["Failed to open document file.", str(e)], nwAlert.ERROR) # Note: Document must be cleared in case of an io error, # or else the auto-save or save will try to overwrite it # with an empty file. Return None to alert the caller. @@ -138,25 +137,23 @@ class NWDoc(): if self.docHandle is None or not self.docEditable: return False - docDir = "content" + self.theProject.ensureFolderStructure() + docFile = self.docHandle+".nwd" - logger.debug("Saving document %s" % path.join(docDir, docFile)) - dataPath = path.join(self.theProject.projPath, docDir) - docPath = path.join(dataPath, docFile) - if not path.isdir(dataPath): - mkdir(dataPath) - logger.debug("Created folder %s" % dataPath) + logger.debug("Saving document %s" % docFile) + + docPath = path.join(self.theProject.projContent, docFile) + docTemp = path.join(self.theProject.projContent, docFile+"~") itemPath = self.theProject.projTree.getItemPath(self.docHandle) docMeta = "%%~ "+":".join(itemPath)+":"+self.theItem.itemName+"\n" - docTemp = path.join(dataPath, docFile+"~") try: - with open(docTemp,mode="w",encoding="utf8") as outFile: + with open(docTemp, mode="w", encoding="utf8") as outFile: outFile.write(docMeta) outFile.write(docText) except Exception as e: - self.makeAlert(["Could not save document.",str(e)], nwAlert.ERROR) + self.makeAlert(["Could not save document.", str(e)], nwAlert.ERROR) return False # If we're here, the file was successfully saved, so we can @@ -173,13 +170,12 @@ class NWDoc(): """Permanently delete a document source file and its backups from the project data folder. """ - docDir = "content" docFile = self.docHandle+".nwd" - dataPath = path.join(self.theProject.projPath, docDir) + chkList = [] - chkList.append(path.join(dataPath, docFile)) - chkList.append(path.join(dataPath, docFile+"~")) - chkList.append(path.join(dataPath, docFile[:-3]+"bak")) + chkList.append(path.join(self.theProject.projContent, docFile)) + chkList.append(path.join(self.theProject.projContent, docFile+"~")) + for chkFile in chkList: if path.isfile(chkFile): try: @@ -188,6 +184,7 @@ class NWDoc(): except Exception as e: self.makeAlert(["Could not delete document file.",str(e)], nwAlert.ERROR) return False + return True ## diff --git a/nw/core/project.py b/nw/core/project.py index abaed265..d7ab3c2d 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -252,17 +252,10 @@ class NWProject(): # Standard Folders and Files # ========================== - self.projMeta = path.join(self.projPath, "meta") - self.projCache = path.join(self.projPath, "cache") - self.projContent = path.join(self.projPath, "content") - self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT) + if not self.ensureFolderStructure(): + return False - if not self._checkFolder(self.projMeta): - return False - if not self._checkFolder(self.projCache): - return False - if not self._checkFolder(self.projContent): - return False + self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT) # Check for Old Legacy Data # ========================= @@ -473,15 +466,8 @@ class NWProject(): ) return False - self.projMeta = path.join(self.projPath, "meta") - self.projContent = path.join(self.projPath, "content") saveTime = time() - - if not self._checkFolder(self.projPath): - return False - if not self._checkFolder(self.projMeta): - return False - if not self._checkFolder(self.projContent): + if not self.ensureFolderStructure(): return False logger.debug("Saving project: %s" % self.projPath) @@ -582,6 +568,26 @@ class NWProject(): self.lockedBy = None return True + def ensureFolderStructure(self): + """Ensure that all necessary folders exist in the project + folder. + """ + if self.projPath is None or self.projPath == "": + return False + + self.projMeta = path.join(self.projPath, "meta") + self.projCache = path.join(self.projPath, "cache") + self.projContent = path.join(self.projPath, "content") + + if not self._checkFolder(self.projMeta): + return False + if not self._checkFolder(self.projCache): + return False + if not self._checkFolder(self.projContent): + return False + + return True + ## # Backup Project ## @@ -1058,7 +1064,7 @@ class NWProject(): def _appendSessionStats(self): """Append session statistics to the sessions log file. """ - if self.projMeta is None: + if not self.ensureFolderStructure(): return False sessionFile = path.join(self.projMeta, nwFiles.SESS_INFO) diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 60752873..a0d7f3ce 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -11,7 +11,7 @@ True True 636b6aa9b697b - ba8a28a246524 + b3e74dbc1f584 914 B @@ -71,7 +71,7 @@ New True PAGE - 208 + 210 40 2 213 From 7b8a852101fb631688a70124c03f0fb1221d368b Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 29 May 2020 23:46:13 +0200 Subject: [PATCH 5/8] Corrected default title formats, and added editTime meta data --- nw/core/project.py | 8 ++++++-- sample/nwProject.nwx | 2 +- tests/reference/gui/0_nwProject.nwx | 2 +- tests/reference/gui/1_nwProject.nwx | 2 +- tests/reference/gui/2_nwProject.nwx | 2 +- tests/reference/gui/3_nwProject.nwx | 2 +- tests/reference/proj/1_nwProject.nwx | 2 +- tests/reference/proj/2_nwProject.nwx | 2 +- tests/reference/proj/3_nwProject.nwx | 2 +- 9 files changed, 14 insertions(+), 10 deletions(-) diff --git a/nw/core/project.py b/nw/core/project.py index d7ab3c2d..132066b3 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -69,6 +69,7 @@ class NWProject(): self.lockedBy = None # Data on which computer has the project open self.saveCount = 0 # Meta data: number of saves self.autoCount = 0 # Meta data: number of automatic saves + self.editTime = 0 # The accumulated edit time read from the project file # Class Settings self.projPath = None # The full path to where the currently open project is saved @@ -206,7 +207,7 @@ class NWProject(): self.autoReplace = {} self.titleFormat = { "title" : r"%title%", - "chapter" : r"Chapter %num%\\%title%", + "chapter" : r"Chapter %ch%: %title%", "unnumbered" : r"%title%", "scene" : r"* * *", "section" : r"", @@ -330,6 +331,8 @@ class NWProject(): self.saveCount = checkInt(xRoot.attrib["saveCount"], 0, False) if "autoCount" in xRoot.attrib: self.autoCount = checkInt(xRoot.attrib["autoCount"], 0, False) + if "editTime" in xRoot.attrib: + self.editTime = checkInt(xRoot.attrib["editTime"], 0, False) logger.verbose("XML root is %s" % nwxRoot) logger.verbose("File version is %s" % fileVersion) @@ -486,6 +489,7 @@ class NWProject(): "saveCount" : str(self.saveCount), "autoCount" : str(self.autoCount), "timeStamp" : formatTimeStamp(saveTime), + "editTime" : str(int(self.editTime + saveTime - self.projOpened)), }) # Save Project Meta @@ -535,7 +539,7 @@ class NWProject(): xml_declaration = True )) except Exception as e: - self.makeAlert(["Failed to save project.",str(e)], nwAlert.ERROR) + self.makeAlert(["Failed to save project.", str(e)], nwAlert.ERROR) return False # If we're here, the file was successfully saved, diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index a0d7f3ce..b70706eb 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project diff --git a/tests/reference/gui/0_nwProject.nwx b/tests/reference/gui/0_nwProject.nwx index 6d2bf739..dcf46837 100644 --- a/tests/reference/gui/0_nwProject.nwx +++ b/tests/reference/gui/0_nwProject.nwx @@ -14,7 +14,7 @@ %title% - Chapter %num%\\%title% + Chapter %ch%: %title% %title% * * *
diff --git a/tests/reference/gui/1_nwProject.nwx b/tests/reference/gui/1_nwProject.nwx index 112a4be0..a09a089f 100644 --- a/tests/reference/gui/1_nwProject.nwx +++ b/tests/reference/gui/1_nwProject.nwx @@ -14,7 +14,7 @@ %title% - Chapter %num%\\%title% + Chapter %ch%: %title% %title% * * *
diff --git a/tests/reference/gui/2_nwProject.nwx b/tests/reference/gui/2_nwProject.nwx index c4668fe4..11fc5dbe 100644 --- a/tests/reference/gui/2_nwProject.nwx +++ b/tests/reference/gui/2_nwProject.nwx @@ -18,7 +18,7 @@
%title% - Chapter %num%\\%title% + Chapter %ch%: %title% %title% * * *
diff --git a/tests/reference/gui/3_nwProject.nwx b/tests/reference/gui/3_nwProject.nwx index 35e1b8df..e7f9e514 100644 --- a/tests/reference/gui/3_nwProject.nwx +++ b/tests/reference/gui/3_nwProject.nwx @@ -14,7 +14,7 @@ %title% - Chapter %num%\\%title% + Chapter %ch%: %title% %title% * * *
diff --git a/tests/reference/proj/1_nwProject.nwx b/tests/reference/proj/1_nwProject.nwx index 53499ddc..5ba28cae 100644 --- a/tests/reference/proj/1_nwProject.nwx +++ b/tests/reference/proj/1_nwProject.nwx @@ -14,7 +14,7 @@ %title% - Chapter %num%\\%title% + Chapter %ch%: %title% %title% * * *
diff --git a/tests/reference/proj/2_nwProject.nwx b/tests/reference/proj/2_nwProject.nwx index 22a9e0fb..fe5bf878 100644 --- a/tests/reference/proj/2_nwProject.nwx +++ b/tests/reference/proj/2_nwProject.nwx @@ -14,7 +14,7 @@ %title% - Chapter %num%\\%title% + Chapter %ch%: %title% %title% * * *
diff --git a/tests/reference/proj/3_nwProject.nwx b/tests/reference/proj/3_nwProject.nwx index ee8f6e1a..2e3cb6c0 100644 --- a/tests/reference/proj/3_nwProject.nwx +++ b/tests/reference/proj/3_nwProject.nwx @@ -14,7 +14,7 @@ %title% - Chapter %num%\\%title% + Chapter %ch%: %title% %title% * * *
From d27e2dc60fbb86c05cc43232236f188da6c95705 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 30 May 2020 00:15:07 +0200 Subject: [PATCH 6/8] Added code to write contents txt and json files on project close --- nw/constants/constants.py | 4 ++-- nw/core/project.py | 48 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/nw/constants/constants.py b/nw/constants/constants.py index cf0ab4eb..f538f2fe 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -39,8 +39,8 @@ class nwFiles(): PROJ_FILE = "nwProject.nwx" PROJ_DICT = "wordlist.txt" PROJ_LOCK = "nwProject.lock" - TOC_TXT = "ToC.txt" - TOC_JSON = "ToC.json" + TOC_TXT = "content.txt" + TOC_JSON = "content.json" SESS_INFO = "sessionInfo.log" INDEX_FILE = "tagsIndex.json" OPTS_FILE = "guiOptions.json" diff --git a/nw/core/project.py b/nw/core/project.py index 132066b3..f2abbcf4 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -31,6 +31,7 @@ """ import logging +import json import nw from os import path, mkdir, listdir, unlink, rename, rmdir @@ -566,6 +567,7 @@ class NWProject(): def closeProject(self): """Close the current project and clear all meta data. """ + self.projTree.writeToCFiles() self._appendSessionStats() self._clearLockFile() self.clearProject() @@ -1282,7 +1284,7 @@ class NWTree(): for tHandle in self._treeOrder: tItem = self.__getitem__(tHandle) tItem.packXML(xContent) - return + return def unpackXML(self, xContent): """Iterate through all items of a content XML object and add @@ -1300,6 +1302,50 @@ class NWTree(): return True + def writeToCFiles(self): + """Write the convenience table of contents files in the root of + the project directory. These files are there to assist the user + if they wish to browse the stored files. + """ + tocText = path.join(self.theProject.projPath, nwFiles.TOC_TXT) + tocJson = path.join(self.theProject.projPath, nwFiles.TOC_JSON) + + jsonData = [] + try: + # Dump the text + with open(tocText, mode="w", encoding="utf8") as outFile: + outFile.write("\n") + outFile.write(" Table of Contents\n") + outFile.write("===================\n") + outFile.write("\n") + outFile.write(" %-25s %-9s %s\n" %("File Name","Class","Document Label")) + outFile.write("-"*80+"\n") + for tHandle in sorted(self._treeOrder): + tItem = self.__getitem__(tHandle) + if tItem is None: + continue + tFile = tHandle+".nwd" + if path.isfile(path.join(self.theProject.projContent, tFile)): + outFile.write(" %-25s %-9s %s\n" %( + path.join("content", tFile), + tItem.itemClass.name, + tItem.itemName, + )) + jsonData.append([ + path.join("content", tFile), + tItem.itemClass.name, + tItem.itemName, + ]) + + # Dump the JSON + with open(tocJson, mode="w+", encoding="utf8") as outFile: + outFile.write(json.dumps(jsonData, indent=2)) + + except Exception as e: + logger.error(str(e)) + + return + ## # Tree Structure Methods ## From a7bca8486f32993b0438163e6be13e25ff8ab4de Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 30 May 2020 00:16:53 +0200 Subject: [PATCH 7/8] Added the ToC files as well to the repo --- nw/constants/constants.py | 4 +- sample/ToC.json | 82 +++++++++++++++++++++++++++++++++++++++ sample/ToC.txt | 22 +++++++++++ sample/nwProject.nwx | 2 +- 4 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 sample/ToC.json create mode 100644 sample/ToC.txt diff --git a/nw/constants/constants.py b/nw/constants/constants.py index f538f2fe..cf0ab4eb 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -39,8 +39,8 @@ class nwFiles(): PROJ_FILE = "nwProject.nwx" PROJ_DICT = "wordlist.txt" PROJ_LOCK = "nwProject.lock" - TOC_TXT = "content.txt" - TOC_JSON = "content.json" + TOC_TXT = "ToC.txt" + TOC_JSON = "ToC.json" SESS_INFO = "sessionInfo.log" INDEX_FILE = "tagsIndex.json" OPTS_FILE = "guiOptions.json" diff --git a/sample/ToC.json b/sample/ToC.json new file mode 100644 index 00000000..4e6ff3d1 --- /dev/null +++ b/sample/ToC.json @@ -0,0 +1,82 @@ +[ + [ + "content/14298de4d9524.nwd", + "CHARACTER", + "John Smith" + ], + [ + "content/53b69b83cdafc.nwd", + "NOVEL", + "Title Page" + ], + [ + "content/5eaea4e8cdee8.nwd", + "WORLD", + "Mars" + ], + [ + "content/636b6aa9b697b.nwd", + "NOVEL", + "Making a Scene" + ], + [ + "content/6a2d6d5f4f401.nwd", + "NOVEL", + "Chapter One" + ], + [ + "content/88706ddc78b1b.nwd", + "NOVEL", + "Chapter Two" + ], + [ + "content/96b68994dfa3d.nwd", + "NOVEL", + "A Note on Structure" + ], + [ + "content/974e400180a99.nwd", + "NOVEL", + "Page" + ], + [ + "content/ae7339df26ded.nwd", + "NOVEL", + "We Found John!" + ], + [ + "content/b3e74dbc1f584.nwd", + "WORLD", + "Earth" + ], + [ + "content/b8136a5a774a0.nwd", + "NOVEL", + "Delete Me!" + ], + [ + "content/ba8a28a246524.nwd", + "NOVEL", + "Interlude" + ], + [ + "content/bb2c23b3c42cc.nwd", + "CHARACTER", + "Jane Smith" + ], + [ + "content/bc0cbd2a407f3.nwd", + "NOVEL", + "Another Scene" + ], + [ + "content/edca4be2fcaf8.nwd", + "NOVEL", + "Part One" + ], + [ + "content/f1471bef9f2ae.nwd", + "WORLD", + "Space" + ] +] \ No newline at end of file diff --git a/sample/ToC.txt b/sample/ToC.txt new file mode 100644 index 00000000..b1a7c2ed --- /dev/null +++ b/sample/ToC.txt @@ -0,0 +1,22 @@ + + Table of Contents +=================== + + File Name Class Document Label +-------------------------------------------------------------------------------- + content/14298de4d9524.nwd CHARACTER John Smith + content/53b69b83cdafc.nwd NOVEL Title Page + content/5eaea4e8cdee8.nwd WORLD Mars + content/636b6aa9b697b.nwd NOVEL Making a Scene + content/6a2d6d5f4f401.nwd NOVEL Chapter One + content/88706ddc78b1b.nwd NOVEL Chapter Two + content/96b68994dfa3d.nwd NOVEL A Note on Structure + content/974e400180a99.nwd NOVEL Page + content/ae7339df26ded.nwd NOVEL We Found John! + content/b3e74dbc1f584.nwd WORLD Earth + content/b8136a5a774a0.nwd NOVEL Delete Me! + content/ba8a28a246524.nwd NOVEL Interlude + content/bb2c23b3c42cc.nwd CHARACTER Jane Smith + content/bc0cbd2a407f3.nwd NOVEL Another Scene + content/edca4be2fcaf8.nwd NOVEL Part One + content/f1471bef9f2ae.nwd WORLD Space diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index b70706eb..f736c544 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project From f3cc6359c4d731a13567dcdb1143346d00410e3a Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 30 May 2020 00:20:14 +0200 Subject: [PATCH 8/8] Add an empty line at the end of the ToC.txt file --- nw/core/project.py | 1 + sample/ToC.txt | 1 + sample/nwProject.nwx | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/nw/core/project.py b/nw/core/project.py index f2abbcf4..13fa1f08 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -1336,6 +1336,7 @@ class NWTree(): tItem.itemClass.name, tItem.itemName, ]) + outFile.write("\n") # Dump the JSON with open(tocJson, mode="w+", encoding="utf8") as outFile: diff --git a/sample/ToC.txt b/sample/ToC.txt index b1a7c2ed..da23e664 100644 --- a/sample/ToC.txt +++ b/sample/ToC.txt @@ -20,3 +20,4 @@ content/bc0cbd2a407f3.nwd NOVEL Another Scene content/edca4be2fcaf8.nwd NOVEL Part One content/f1471bef9f2ae.nwd WORLD Space + diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index f736c544..4a88e0c1 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project