From c6d3f680ccc752656b97e498d037e4757adec89e Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sat, 5 Nov 2022 17:03:45 +0100
Subject: [PATCH] Add a UUID to projects
---
novelwriter/common.py | 10 ++++++++
novelwriter/core/coretools.py | 1 +
novelwriter/core/projectdata.py | 16 ++++++++++++-
novelwriter/core/projectxml.py | 3 ++-
sample/nwProject.nwx | 8 +++----
tests/files/nwProject-1.4.nwx | 2 +-
tests/lipsum/nwProject.nwx | 9 ++++---
.../coreProject_NewFileFolder_nwProject.nwx | 4 ++--
.../coreProject_NewRoot_nwProject.nwx | 4 ++--
.../coreTools_NewCustomA_nwProject.nwx | 4 ++--
.../coreTools_NewCustomB_nwProject.nwx | 4 ++--
.../coreTools_NewMinimal_nwProject.nwx | 4 ++--
.../guiEditor_Main_Final_nwProject.nwx | 6 ++---
.../guiEditor_Main_Initial_nwProject.nwx | 4 ++--
tests/reference/projectXML_ReadLegacy10.nwx | 2 +-
tests/reference/projectXML_ReadLegacy11.nwx | 2 +-
tests/reference/projectXML_ReadLegacy12.nwx | 2 +-
tests/reference/projectXML_ReadLegacy13.nwx | 2 +-
tests/test_base/test_base_common.py | 24 +++++++++++++++----
tests/test_core/test_core_coretools.py | 13 +++++++---
tests/test_core/test_core_projectxml.py | 6 ++++-
tests/tools.py | 1 +
22 files changed, 91 insertions(+), 40 deletions(-)
diff --git a/novelwriter/common.py b/novelwriter/common.py
index 60d1f7fa..5a84302d 100644
--- a/novelwriter/common.py
+++ b/novelwriter/common.py
@@ -25,6 +25,7 @@ along with this program. If not, see .
import os
import json
+import uuid
import hashlib
import logging
@@ -113,6 +114,15 @@ def checkHandle(value, default, allowNone=False):
return default
+def checkUuid(value, default):
+ """Try to process a value as an uuid, or return a default.
+ """
+ try:
+ return str(uuid.UUID(value))
+ except Exception:
+ return default
+
+
# =============================================================================================== #
# Validator Functions
# =============================================================================================== #
diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py
index 0c24fc29..1ff73d2e 100644
--- a/novelwriter/core/coretools.py
+++ b/novelwriter/core/coretools.py
@@ -322,6 +322,7 @@ class ProjectBuilder:
projTitle = data.get("projTitle", lblNewProject)
projAuthors = data.get("projAuthors", "")
+ project.data.setUuid(None)
project.data.setName(projName)
project.data.setTitle(projTitle)
project.data.setAuthors(projAuthors)
diff --git a/novelwriter/core/projectdata.py b/novelwriter/core/projectdata.py
index 913a8aa8..40bce1a7 100644
--- a/novelwriter/core/projectdata.py
+++ b/novelwriter/core/projectdata.py
@@ -25,9 +25,12 @@ along with this program. If not, see .
from __future__ import annotations
+import uuid
import logging
-from novelwriter.common import checkBool, checkInt, checkStringNone, isHandle, simplified
+from novelwriter.common import (
+ checkBool, checkInt, checkStringNone, checkUuid, isHandle, simplified
+)
from novelwriter.core.status import NWStatus
logger = logging.getLogger(__name__)
@@ -196,6 +199,17 @@ class NWProjectData:
# Setters
##
+ def setUuid(self, value):
+ """Set the project id.
+ """
+ value = checkUuid(value, "")
+ if not value:
+ self._uuid = str(uuid.uuid4())
+ elif value != self._uuid:
+ self._uuid = value
+ self.theProject.setProjectChanged(True)
+ return
+
def setName(self, value):
"""Set a new project name.
"""
diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py
index 8f15b0f5..ed0d2e9a 100644
--- a/novelwriter/core/projectxml.py
+++ b/novelwriter/core/projectxml.py
@@ -224,6 +224,7 @@ class ProjectXMLReader:
"""Parse the project section of the XML file.
"""
logger.debug("Parsing section")
+ projData.setUuid(xSection.attrib.get("id", None))
for xItem in xSection:
if xItem.tag == "name":
projData.setName(xItem.text)
@@ -476,7 +477,7 @@ class ProjectXMLWriter:
})
# Save Project Meta
- xProject = etree.SubElement(xRoot, "project")
+ xProject = etree.SubElement(xRoot, "project", attrib={"id": projData.uuid})
self._packSingleValue(xProject, "name", projData.name)
self._packSingleValue(xProject, "title", projData.title)
self._packListValue(xProject, "author", projData.authors)
diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx
index 1dba26ca..d7d7bde1 100644
--- a/sample/nwProject.nwx
+++ b/sample/nwProject.nwx
@@ -1,13 +1,13 @@
-
-
+
+
Sample Project
Sample Project
Jane Smith
Jay Doh
- 1409
+ 1421
236
- 69427
+ 69454
False
diff --git a/tests/files/nwProject-1.4.nwx b/tests/files/nwProject-1.4.nwx
index 706b2266..71baddbc 100644
--- a/tests/files/nwProject-1.4.nwx
+++ b/tests/files/nwProject-1.4.nwx
@@ -1,6 +1,6 @@
-
+
Sample Project
Sample Project
Jane Smith
diff --git a/tests/lipsum/nwProject.nwx b/tests/lipsum/nwProject.nwx
index 5ac1b4de..d78a4aa0 100644
--- a/tests/lipsum/nwProject.nwx
+++ b/tests/lipsum/nwProject.nwx
@@ -1,19 +1,18 @@
-
-
+
+
Lorem Ipsum
Lorem Ipsum
lipsum.com
- 32
+ 34
24
- 1889
+ 1893
False
en_GB
False
None
- 3847
3109
738
diff --git a/tests/reference/coreProject_NewFileFolder_nwProject.nwx b/tests/reference/coreProject_NewFileFolder_nwProject.nwx
index b1370820..973306cf 100644
--- a/tests/reference/coreProject_NewFileFolder_nwProject.nwx
+++ b/tests/reference/coreProject_NewFileFolder_nwProject.nwx
@@ -1,6 +1,6 @@
-
-
+
+
New Project
New Novel
Jane Doe
diff --git a/tests/reference/coreProject_NewRoot_nwProject.nwx b/tests/reference/coreProject_NewRoot_nwProject.nwx
index ed50e9ca..6aea2f22 100644
--- a/tests/reference/coreProject_NewRoot_nwProject.nwx
+++ b/tests/reference/coreProject_NewRoot_nwProject.nwx
@@ -1,6 +1,6 @@
-
-
+
+
New Project
New Novel
Jane Doe
diff --git a/tests/reference/coreTools_NewCustomA_nwProject.nwx b/tests/reference/coreTools_NewCustomA_nwProject.nwx
index f6314c1a..1b083ddc 100644
--- a/tests/reference/coreTools_NewCustomA_nwProject.nwx
+++ b/tests/reference/coreTools_NewCustomA_nwProject.nwx
@@ -1,6 +1,6 @@
-
-
+
+
Test Custom
Test Novel
Jane Doe
diff --git a/tests/reference/coreTools_NewCustomB_nwProject.nwx b/tests/reference/coreTools_NewCustomB_nwProject.nwx
index 23e0c509..637afa4f 100644
--- a/tests/reference/coreTools_NewCustomB_nwProject.nwx
+++ b/tests/reference/coreTools_NewCustomB_nwProject.nwx
@@ -1,6 +1,6 @@
-
-
+
+
Test Custom
Test Novel
Jane Doe
diff --git a/tests/reference/coreTools_NewMinimal_nwProject.nwx b/tests/reference/coreTools_NewMinimal_nwProject.nwx
index 0f2388f5..bed1b6bf 100644
--- a/tests/reference/coreTools_NewMinimal_nwProject.nwx
+++ b/tests/reference/coreTools_NewMinimal_nwProject.nwx
@@ -1,6 +1,6 @@
-
-
+
+
New Project
New Project
1
diff --git a/tests/reference/guiEditor_Main_Final_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx
index 853ea1d9..2c849f63 100644
--- a/tests/reference/guiEditor_Main_Final_nwProject.nwx
+++ b/tests/reference/guiEditor_Main_Final_nwProject.nwx
@@ -1,12 +1,12 @@
-
-
+
+
New Project
New Novel
Jane Doe
4
2
- 3
+ 4
True
diff --git a/tests/reference/guiEditor_Main_Initial_nwProject.nwx b/tests/reference/guiEditor_Main_Initial_nwProject.nwx
index 8108fba8..84c353e8 100644
--- a/tests/reference/guiEditor_Main_Initial_nwProject.nwx
+++ b/tests/reference/guiEditor_Main_Initial_nwProject.nwx
@@ -1,6 +1,6 @@
-
-
+
+
New Project
New Novel
Jane Doe
diff --git a/tests/reference/projectXML_ReadLegacy10.nwx b/tests/reference/projectXML_ReadLegacy10.nwx
index 8e7f0a99..b557dc19 100644
--- a/tests/reference/projectXML_ReadLegacy10.nwx
+++ b/tests/reference/projectXML_ReadLegacy10.nwx
@@ -1,6 +1,6 @@
-
+
Sample Project
Sample Project
Jane Smith
diff --git a/tests/reference/projectXML_ReadLegacy11.nwx b/tests/reference/projectXML_ReadLegacy11.nwx
index fce991d3..84df9eef 100644
--- a/tests/reference/projectXML_ReadLegacy11.nwx
+++ b/tests/reference/projectXML_ReadLegacy11.nwx
@@ -1,6 +1,6 @@
-
+
Sample Project
Sample Project
Jane Smith
diff --git a/tests/reference/projectXML_ReadLegacy12.nwx b/tests/reference/projectXML_ReadLegacy12.nwx
index 9de6966b..e87015fe 100644
--- a/tests/reference/projectXML_ReadLegacy12.nwx
+++ b/tests/reference/projectXML_ReadLegacy12.nwx
@@ -1,6 +1,6 @@
-
+
Sample Project
Sample Project
Jane Smith
diff --git a/tests/reference/projectXML_ReadLegacy13.nwx b/tests/reference/projectXML_ReadLegacy13.nwx
index 7fc4cfea..a8be3ecd 100644
--- a/tests/reference/projectXML_ReadLegacy13.nwx
+++ b/tests/reference/projectXML_ReadLegacy13.nwx
@@ -1,6 +1,6 @@
-
+
Sample Project
Sample Project
Jane Smith
diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py
index bb05265f..ca487aa2 100644
--- a/tests/test_base/test_base_common.py
+++ b/tests/test_base/test_base_common.py
@@ -30,11 +30,11 @@ from tools import writeFile
from novelwriter.guimain import GuiMain
from novelwriter.common import (
checkStringNone, checkString, checkInt, checkFloat, checkBool, checkHandle,
- isHandle, isTitleTag, isItemClass, isItemType, isItemLayout, hexToInt,
- minmax, checkIntTuple, formatInt, formatTimeStamp, formatTime, simplified,
- splitVersionNumber, transferCase, fuzzyTime, numberToRoman, jsonEncode,
- readTextFile, makeFileNameSafe, ensureFolder, sha256sum, getGuiItem,
- NWConfigParser
+ checkUuid, isHandle, isTitleTag, isItemClass, isItemType, isItemLayout,
+ hexToInt, minmax, checkIntTuple, formatInt, formatTimeStamp, formatTime,
+ simplified, splitVersionNumber, transferCase, fuzzyTime, numberToRoman,
+ jsonEncode, readTextFile, makeFileNameSafe, ensureFolder, sha256sum,
+ getGuiItem, NWConfigParser
)
@@ -137,6 +137,20 @@ def testBaseCommon_CheckHandle():
# END Test testBaseCommon_CheckHandle
+@pytest.mark.base
+def testBaseCommon_CheckUuid():
+ """Test the checkUuid function.
+ """
+ testUuid = "e2be99af-f9bf-4403-857a-c3d1ac25abea"
+ assert checkUuid("", None) is None
+ assert checkUuid("e2be99af-f9bf-4403-857a-c3d1ac25abe", None) is None
+ assert checkUuid("e2be99af-f9bf-qq03-857a-c3d1ac25abea", None) is None
+ assert checkUuid("e2be99af-f9bf-4403-857a-c3d1ac25abeaa", None) is None
+ assert checkUuid(testUuid, None) == testUuid
+
+# END Test testBaseCommon_CheckUuid
+
+
@pytest.mark.base
def testBaseCommon_IsHandle():
"""Test the isHandle function.
diff --git a/tests/test_core/test_core_coretools.py b/tests/test_core/test_core_coretools.py
index f98e32f1..afb4d07b 100644
--- a/tests/test_core/test_core_coretools.py
+++ b/tests/test_core/test_core_coretools.py
@@ -20,6 +20,7 @@ along with this program. If not, see .
"""
import os
+import uuid
import pytest
from shutil import copyfile
@@ -264,10 +265,12 @@ def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, mock
@pytest.mark.core
-def testCoreTools_NewMinimal(fncDir, outDir, refDir, mockGUI, mockRnd):
+def testCoreTools_NewMinimal(monkeypatch, fncDir, outDir, refDir, mockGUI, mockRnd):
"""Create a new project from a project wizard dictionary. With
default setting, creating a Minimal project.
"""
+ monkeypatch.setattr("uuid.uuid4", lambda *a: uuid.UUID("d0f3fe10-c6e6-4310-8bfd-181eb4224eed"))
+
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreTools_NewMinimal_nwProject.nwx")
compFile = os.path.join(refDir, "coreTools_NewMinimal_nwProject.nwx")
@@ -294,10 +297,12 @@ def testCoreTools_NewMinimal(fncDir, outDir, refDir, mockGUI, mockRnd):
@pytest.mark.core
-def testCoreTools_NewCustomA(fncDir, outDir, refDir, mockGUI, mockRnd):
+def testCoreTools_NewCustomA(monkeypatch, fncDir, outDir, refDir, mockGUI, mockRnd):
"""Create a new project from a project wizard dictionary.
Custom type with chapters and scenes.
"""
+ monkeypatch.setattr("uuid.uuid4", lambda *a: uuid.UUID("d0f3fe10-c6e6-4310-8bfd-181eb4224eed"))
+
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreTools_NewCustomA_nwProject.nwx")
compFile = os.path.join(refDir, "coreTools_NewCustomA_nwProject.nwx")
@@ -330,10 +335,12 @@ def testCoreTools_NewCustomA(fncDir, outDir, refDir, mockGUI, mockRnd):
@pytest.mark.core
-def testCoreTools_NewCustomB(fncDir, outDir, refDir, mockGUI, mockRnd):
+def testCoreTools_NewCustomB(monkeypatch, fncDir, outDir, refDir, mockGUI, mockRnd):
"""Create a new project from a project wizard dictionary.
Custom type without chapters, but with scenes.
"""
+ monkeypatch.setattr("uuid.uuid4", lambda *a: uuid.UUID("d0f3fe10-c6e6-4310-8bfd-181eb4224eed"))
+
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreTools_NewCustomB_nwProject.nwx")
compFile = os.path.join(refDir, "coreTools_NewCustomB_nwProject.nwx")
diff --git a/tests/test_core/test_core_projectxml.py b/tests/test_core/test_core_projectxml.py
index ac2b4a3c..b3af7289 100644
--- a/tests/test_core/test_core_projectxml.py
+++ b/tests/test_core/test_core_projectxml.py
@@ -232,7 +232,7 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, tstPaths, fncPath):
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
copyfile(outFile, tstFile)
- assert cmpFiles(tstFile, xmlFile)
+ assert cmpFiles(tstFile, refFile)
# END Test testCoreProjectXML_ReadCurrent
@@ -369,6 +369,7 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockRnd):
# Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncPath)
+ data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
testFile = tstPaths.outDir / "projectXML_ReadLegacy10.nwx"
compFile = tstPaths.refDir / "projectXML_ReadLegacy10.nwx"
@@ -510,6 +511,7 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockRnd):
# Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncPath)
+ data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
testFile = tstPaths.outDir / "projectXML_ReadLegacy11.nwx"
compFile = tstPaths.refDir / "projectXML_ReadLegacy11.nwx"
@@ -654,6 +656,7 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockRnd):
# Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncPath)
+ data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
testFile = tstPaths.outDir / "projectXML_ReadLegacy12.nwx"
compFile = tstPaths.refDir / "projectXML_ReadLegacy12.nwx"
@@ -798,6 +801,7 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockRnd):
# Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncPath)
+ data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
testFile = tstPaths.outDir / "projectXML_ReadLegacy13.nwx"
compFile = tstPaths.refDir / "projectXML_ReadLegacy13.nwx"
diff --git a/tests/tools.py b/tests/tools.py
index 5094fa62..7824268b 100644
--- a/tests/tools.py
+++ b/tests/tools.py
@@ -170,6 +170,7 @@ def buildTestProject(theObject, projPath):
theProject.storage.openProjectInPlace(theProject.projPath)
theProject.setDefaultStatusImport()
+ theProject.data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
theProject.data.setName("New Project")
theProject.data.setTitle("New Novel")
theProject.data.setAuthors("Jane Doe")