From 192427deaa69b460963da56c0cf0904e246b9956 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 28 May 2020 18:55:44 +0200
Subject: [PATCH] Changed the data structure of the project folder
---
nw/core/document.py | 33 ++++----------
nw/core/project.py | 102 +++++++++++++++++++++++++++++++++++++++++---
nw/core/tools.py | 7 +--
3 files changed, 104 insertions(+), 38 deletions(-)
diff --git a/nw/core/document.py b/nw/core/document.py
index 4cfd09a3..a28804a9 100644
--- a/nw/core/document.py
+++ b/nw/core/document.py
@@ -93,8 +93,9 @@ class NWDoc():
if self.theItem.parHandle == self.theProject.projTree.trashRoot():
self.docEditable = False
- docDir, docFile = self._assemblePath(self.docHandle, self.FILE_MN)
- self.fileLoc = path.join(docDir,docFile)
+ 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)
@@ -139,8 +140,9 @@ class NWDoc():
if self.docHandle is None or not self.docEditable:
return False
- docDir, docFile = self._assemblePath(self.docHandle, self.FILE_MN)
- logger.debug("Saving document %s" % path.join(docDir,docFile))
+ docDir = "content"
+ 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):
@@ -159,12 +161,6 @@ class NWDoc():
self.makeAlert(["Could not save document.",str(e)], nwAlert.ERROR)
return False
- # Remove bak files from old file save method, if one exists
- # This part can eventually be removed
- docBack = path.join(dataPath, docFile[:-3]+"bak")
- if path.isfile(docBack):
- unlink(docBack)
-
# If we're here, the file was successfully saved, so we can
# replace the temp file with the actual file
if path.isfile(docPath):
@@ -179,7 +175,8 @@ class NWDoc():
"""Permanently delete a document source file and its backups
from the project data folder.
"""
- docDir, docFile = self._assemblePath(tHandle, self.FILE_MN)
+ docDir = "content"
+ docFile = self.docHandle+".nwd"
dataPath = path.join(self.theProject.projPath, docDir)
chkList = []
chkList.append(path.join(dataPath, docFile))
@@ -226,18 +223,4 @@ class NWDoc():
return theMeta, thePath
- ##
- # Internal Functions
- ##
-
- @staticmethod
- def _assemblePath(tHandle, docExt):
- """Assemble the file path for a given handle.
- """
- if tHandle is None:
- return None, None
- docDir = "data_"+tHandle[0]
- docFile = tHandle[1:13]+"_"+docExt
- return docDir, docFile
-
# END Class NWDoc
diff --git a/nw/core/project.py b/nw/core/project.py
index 0c303f74..fbf04177 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -33,7 +33,7 @@
import logging
import nw
-from os import path, mkdir, listdir, unlink, rename
+from os import path, mkdir, listdir, unlink, rename, rmdir
from lxml import etree
from hashlib import sha256
from time import time
@@ -74,6 +74,7 @@ class NWProject():
# 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
@@ -195,6 +196,7 @@ class NWProject():
# Project Settings
self.projPath = None
self.projMeta = None
+ self.projData = None
self.projDict = None
self.projFile = nwFiles.PROJ_FILE
self.projName = ""
@@ -247,10 +249,13 @@ class NWProject():
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)
if not self._checkFolder(self.projMeta):
return False
+ if not self._checkFolder(self.projData):
+ return False
if overrideLock:
self._clearLockFile()
@@ -291,7 +296,7 @@ class NWProject():
self.clearProject()
return False
- xRoot = nwXML.getroot()
+ xRoot = nwXML.getroot()
nwxRoot = xRoot.tag
appVersion = "Unknown"
@@ -314,13 +319,40 @@ class NWProject():
logger.verbose("XML root is %s" % nwxRoot)
logger.verbose("File version is %s" % fileVersion)
- if not nwxRoot == "novelWriterXML" or not fileVersion == "1.0":
+ # Check File Type
+ # ===============
+ if not nwxRoot == "novelWriterXML":
self.makeAlert(
- "Project file does not appear to be a novelWriterXML file version 1.0",
+ "Project file does not appear to be a novelWriterXML file.",
nwAlert.ERROR
)
return False
+ # 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__)
+ if msgRes == QMessageBox.Yes:
+ self._updateStorage()
+ else:
+ return False
+ elif fileVersion != "1.1":
+ self.makeAlert((
+ "Unknown or unsupported %s project format. "
+ "The project cannot be opened by this version of %s."
+ ) % (
+ nw.__package__, nw.__package__
+ ), nwAlert.ERROR)
+ return False
+
+ # Check novelWriter Version
+ # =========================
if int(hexVersion, 16) > int(nw.__hexversion__, 16) and self.mainConf.showGUI:
msgBox = QMessageBox()
msgRes = msgBox.question(self.theParent, "Version Conflict", (
@@ -333,6 +365,8 @@ class NWProject():
if msgRes != QMessageBox.Yes:
return False
+ # Start Parsing XML
+ # =================
for xChild in xRoot:
if xChild.tag == "project":
logger.debug("Found project meta")
@@ -404,16 +438,21 @@ class NWProject():
file.
"""
if self.projPath is None:
- self.makeAlert("Project path not set, cannot save.", nwAlert.ERROR)
+ self.makeAlert(
+ "Project path not set, cannot save project.", nwAlert.ERROR
+ )
return False
- self.projMeta = path.join(self.projPath,"meta")
+ self.projMeta = path.join(self.projPath, "meta")
+ self.projData = 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):
+ return False
logger.debug("Saving project: %s" % self.projPath)
@@ -427,7 +466,7 @@ class NWProject():
nwXML = etree.Element("novelWriterXML",attrib={
"appVersion" : str(nw.__version__),
"hexVersion" : str(nw.__hexversion__),
- "fileVersion" : "1.0",
+ "fileVersion" : "1.1",
"saveCount" : str(self.saveCount),
"autoCount" : str(self.autoCount),
"timeStamp" : formatTimeStamp(saveTime),
@@ -1016,6 +1055,55 @@ class NWProject():
return True
+ def _updateStorage(self):
+ """Updates the project storage folder from 1.0 to 1.1.
+ """
+ contDir = path.join(self.projPath, "content")
+ self._checkFolder(contDir)
+ errList = []
+
+ for projItem in listdir(self.projPath):
+ itemPath = path.join(self.projPath, projItem)
+ if not path.isdir(itemPath) or not projItem.startswith("data_"):
+ 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)
+
+ 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)
+
+ if errList:
+ self.makeAlert(errList, nwAlert.ERROR)
+
+ return
+
# END Class NWProject
# ================================================================================================ #
diff --git a/nw/core/tools.py b/nw/core/tools.py
index a6eb244b..8aa81d7b 100644
--- a/nw/core/tools.py
+++ b/nw/core/tools.py
@@ -89,7 +89,7 @@ def projectMaintenance(theProject):
if path.isdir(theProject.projPath):
cacheDir = path.join(theProject.projPath, "cache")
if path.isdir(cacheDir):
- logger.info("Deprecated cache folder found")
+ logger.info("Deprecated cache folder content found")
rmList = []
for i in range(10):
rmList.append(path.join(cacheDir, "nwProject.nwx.%d" % i))
@@ -101,11 +101,6 @@ def projectMaintenance(theProject):
unlink(rmFile)
except Exception as e:
logger.error(str(e))
- logger.info("Deleting: %s" % cacheDir)
- try:
- rmdir(cacheDir)
- except Exception as e:
- logger.error(str(e))
# Remove no longer used meta files
rmList = []