Add a UUID to projects
This commit is contained in:
@@ -25,6 +25,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
# =============================================================================================== #
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -25,9 +25,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
@@ -224,6 +224,7 @@ class ProjectXMLReader:
|
||||
"""Parse the project section of the XML file.
|
||||
"""
|
||||
logger.debug("Parsing <project> 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)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 12:20:53">
|
||||
<project>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-05 16:59:57">
|
||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea">
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
<author>Jay Doh</author>
|
||||
<saveCount>1409</saveCount>
|
||||
<saveCount>1421</saveCount>
|
||||
<autoCount>236</autoCount>
|
||||
<editTime>69427</editTime>
|
||||
<editTime>69454</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>False</doBackup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 12:20:53">
|
||||
<project>
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed">
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-31 22:17:38">
|
||||
<project>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-05 16:38:19">
|
||||
<project id="5ac0df12-8b8b-476b-9905-21d33685687c">
|
||||
<name>Lorem Ipsum</name>
|
||||
<title>Lorem Ipsum</title>
|
||||
<author>lipsum.com</author>
|
||||
<saveCount>32</saveCount>
|
||||
<saveCount>34</saveCount>
|
||||
<autoCount>24</autoCount>
|
||||
<editTime>1889</editTime>
|
||||
<editTime>1893</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>False</doBackup>
|
||||
<language>en_GB</language>
|
||||
<spellCheck>False</spellCheck>
|
||||
<spellLang>None</spellLang>
|
||||
<totalWordCount>3847</totalWordCount>
|
||||
<novelWordCount>3109</novelWordCount>
|
||||
<notesWordCount>738</notesWordCount>
|
||||
<lastHandle>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:22">
|
||||
<project>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-05 16:43:09">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed">
|
||||
<name>New Project</name>
|
||||
<title>New Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:22">
|
||||
<project>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-05 16:43:09">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed">
|
||||
<name>New Project</name>
|
||||
<title>New Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-03 23:38:30">
|
||||
<project>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-05 16:57:31">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed">
|
||||
<name>Test Custom</name>
|
||||
<title>Test Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-03 23:37:43">
|
||||
<project>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-05 16:58:05">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed">
|
||||
<name>Test Custom</name>
|
||||
<title>Test Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-03 23:33:20">
|
||||
<project>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-05 16:58:05">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed">
|
||||
<name>New Project</name>
|
||||
<title>New Project</title>
|
||||
<saveCount>1</saveCount>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 12:01:37">
|
||||
<project>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-05 16:40:49">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed">
|
||||
<name>New Project</name>
|
||||
<title>New Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
<saveCount>4</saveCount>
|
||||
<autoCount>2</autoCount>
|
||||
<editTime>3</editTime>
|
||||
<editTime>4</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>True</doBackup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:30">
|
||||
<project>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-05 16:37:48">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed">
|
||||
<name>New Project</name>
|
||||
<title>New Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2020-05-28 09:59:15">
|
||||
<project>
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed">
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2020-06-26 21:20:24">
|
||||
<project>
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed">
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2021-08-30 23:33:44">
|
||||
<project>
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed">
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-25 18:26:15">
|
||||
<project>
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed">
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -20,6 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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")
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user