Remove the old legacy format code
This commit is contained in:
@@ -378,9 +378,6 @@ class NWProject(QObject):
|
||||
self._options.loadSettings()
|
||||
self._index.loadIndex()
|
||||
|
||||
# Clean up no longer used files
|
||||
self._deprecatedFiles()
|
||||
|
||||
# Update recent projects
|
||||
self.mainConf.updateRecentCache(
|
||||
self.projPath, self._data.name, sum(self._data.initCounts), time()
|
||||
@@ -1046,77 +1043,6 @@ class NWProject(QObject):
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Legacy Data Structure Handlers
|
||||
##
|
||||
|
||||
def _legacyDataFolder(self, dataDir):
|
||||
"""Clean up legacy data folders.
|
||||
"""
|
||||
dataPath = os.path.join(self.projPath, dataDir)
|
||||
if not os.path.isdir(dataPath):
|
||||
return False
|
||||
|
||||
logger.info("Old data folder found: %s", dataDir)
|
||||
|
||||
# Move Documents to Content
|
||||
for dataItem in os.listdir(dataPath):
|
||||
dataFile = os.path.join(dataPath, dataItem)
|
||||
if not os.path.isfile(dataFile):
|
||||
continue
|
||||
|
||||
if len(dataItem) == 21 and dataItem.endswith("_main.nwd"):
|
||||
tHandle = dataDir[-1] + dataItem[:12]
|
||||
newPath = os.path.join(self.projContent, f"{tHandle}.nwd")
|
||||
os.rename(dataFile, newPath)
|
||||
logger.info("Moved file: %s", dataFile)
|
||||
|
||||
elif len(dataItem) == 21 and dataItem.endswith("_main.bak"):
|
||||
os.unlink(dataFile)
|
||||
logger.info("Deleted file: %s", dataFile)
|
||||
|
||||
# Remove Data Folder
|
||||
if not os.listdir(dataPath):
|
||||
os.rmdir(dataPath)
|
||||
logger.info("Deleted folder: %s", dataDir)
|
||||
|
||||
return True
|
||||
|
||||
def _deprecatedFiles(self):
|
||||
"""Delete files that are no longer used by novelWriter.
|
||||
"""
|
||||
rmList = [
|
||||
os.path.join(self.projCache, "nwProject.nwx.0"),
|
||||
os.path.join(self.projCache, "nwProject.nwx.1"),
|
||||
os.path.join(self.projCache, "nwProject.nwx.2"),
|
||||
os.path.join(self.projCache, "nwProject.nwx.3"),
|
||||
os.path.join(self.projCache, "nwProject.nwx.4"),
|
||||
os.path.join(self.projCache, "nwProject.nwx.5"),
|
||||
os.path.join(self.projCache, "nwProject.nwx.6"),
|
||||
os.path.join(self.projCache, "nwProject.nwx.7"),
|
||||
os.path.join(self.projCache, "nwProject.nwx.8"),
|
||||
os.path.join(self.projCache, "nwProject.nwx.9"),
|
||||
os.path.join(self.projMeta, "mainOptions.json"),
|
||||
os.path.join(self.projMeta, "exportOptions.json"),
|
||||
os.path.join(self.projMeta, "outlineOptions.json"),
|
||||
os.path.join(self.projMeta, "timelineOptions.json"),
|
||||
os.path.join(self.projMeta, "docMergeOptions.json"),
|
||||
os.path.join(self.projMeta, "sessionLogOptions.json"),
|
||||
os.path.join(self.projPath, "ToC.json"),
|
||||
]
|
||||
|
||||
for rmFile in rmList:
|
||||
if os.path.isfile(rmFile):
|
||||
logger.info("Deleting: %s", rmFile)
|
||||
try:
|
||||
os.unlink(rmFile)
|
||||
except Exception:
|
||||
logger.error("Could not delete: %s", rmFile)
|
||||
logException()
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# END Class NWProject
|
||||
|
||||
|
||||
|
||||
@@ -267,27 +267,6 @@ def nwLipsum(tmpDir):
|
||||
return
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def nwOldProj(tmpDir):
|
||||
"""A minimal movelWriter project using the old folder structure used
|
||||
for storage versions < 1.2.
|
||||
"""
|
||||
tstDir = os.path.dirname(__file__)
|
||||
srcDir = os.path.join(tstDir, "oldproj")
|
||||
dstDir = os.path.join(tmpDir, "oldproj")
|
||||
if os.path.isdir(dstDir):
|
||||
shutil.rmtree(dstDir)
|
||||
|
||||
shutil.copytree(srcDir, dstDir)
|
||||
|
||||
yield dstDir
|
||||
|
||||
if os.path.isdir(dstDir):
|
||||
shutil.rmtree(dstDir)
|
||||
|
||||
return
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ipsumText():
|
||||
"""Return five paragraphs of Lorem Ipsum text.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
### Scene Four
|
||||
|
||||
Scene Four
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Antagonist
|
||||
|
||||
Antagonist
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
### Scene Two
|
||||
|
||||
Scene Two
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Protagonist
|
||||
|
||||
Protagonist
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
### Scene Three
|
||||
|
||||
Scene Three
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
### Scene Five
|
||||
|
||||
Scene Five
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
### Scene One
|
||||
|
||||
Scene One
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
Start: 2020-09-26 16:13:00 End: 2020-09-26 16:15:54 Words: 24
|
||||
Start: 2020-09-26 16:16:28 End: 2020-09-26 16:16:40 Words: -1
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"tagIndex": {},
|
||||
"refIndex": {
|
||||
"f528d831f5b24": [],
|
||||
"88124a4292d8b": [],
|
||||
"91239bf2f8b69": [],
|
||||
"19752e7f9d8af": [],
|
||||
"a764d5acf5a21": [],
|
||||
"9058ae29f0dfd": [],
|
||||
"7ff63b8afc4cd": []
|
||||
},
|
||||
"novelIndex": {
|
||||
"f528d831f5b24": [
|
||||
[
|
||||
1,
|
||||
3,
|
||||
"Scene One",
|
||||
"SCENE"
|
||||
]
|
||||
],
|
||||
"88124a4292d8b": [
|
||||
[
|
||||
1,
|
||||
3,
|
||||
"Scene Two",
|
||||
"SCENE"
|
||||
]
|
||||
],
|
||||
"91239bf2f8b69": [
|
||||
[
|
||||
1,
|
||||
3,
|
||||
"Scene Three",
|
||||
"SCENE"
|
||||
]
|
||||
],
|
||||
"19752e7f9d8af": [
|
||||
[
|
||||
1,
|
||||
3,
|
||||
"Scene Four",
|
||||
"SCENE"
|
||||
]
|
||||
],
|
||||
"a764d5acf5a21": [
|
||||
[
|
||||
1,
|
||||
3,
|
||||
"Scene Five",
|
||||
"SCENE"
|
||||
]
|
||||
]
|
||||
},
|
||||
"noteIndex": {
|
||||
"9058ae29f0dfd": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
"Protagonist",
|
||||
"NOTE"
|
||||
]
|
||||
],
|
||||
"7ff63b8afc4cd": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
"Antagonist",
|
||||
"NOTE"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="0.4.5" fileVersion="1.0" timeStamp="2020-09-26 16:16:39">
|
||||
<project>
|
||||
<name></name>
|
||||
<title></title>
|
||||
<backup>True</backup>
|
||||
</project>
|
||||
<settings>
|
||||
<spellCheck>False</spellCheck>
|
||||
<lastEdited>a764d5acf5a21</lastEdited>
|
||||
<lastViewed>None</lastViewed>
|
||||
<lastWordCount>23</lastWordCount>
|
||||
<autoReplace/>
|
||||
<status>
|
||||
<entry blue="100" green="100" red="100">New</entry>
|
||||
<entry blue="0" green="50" red="200">Note</entry>
|
||||
<entry blue="0" green="150" red="200">Draft</entry>
|
||||
<entry blue="0" green="200" red="50">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry blue="100" green="100" red="100">New</entry>
|
||||
<entry blue="0" green="50" red="200">Minor</entry>
|
||||
<entry blue="0" green="150" red="200">Major</entry>
|
||||
<entry blue="0" green="200" red="50">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="12">
|
||||
<item handle="095c337ad50c8" order="0" parent="None">
|
||||
<name>Novel</name>
|
||||
<type>ROOT</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>True</expanded>
|
||||
</item>
|
||||
<item handle="04d47c4b31af7" order="0" parent="095c337ad50c8">
|
||||
<name>Chapter One</name>
|
||||
<type>FOLDER</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>True</expanded>
|
||||
</item>
|
||||
<item handle="f528d831f5b24" order="0" parent="04d47c4b31af7">
|
||||
<name>Scene One</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>18</charCount>
|
||||
<wordCount>4</wordCount>
|
||||
<paraCount>1</paraCount>
|
||||
<cursorPos>3</cursorPos>
|
||||
</item>
|
||||
<item handle="88124a4292d8b" order="1" parent="04d47c4b31af7">
|
||||
<name>Scene Two</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>18</charCount>
|
||||
<wordCount>4</wordCount>
|
||||
<paraCount>1</paraCount>
|
||||
<cursorPos>2</cursorPos>
|
||||
</item>
|
||||
<item handle="91239bf2f8b69" order="2" parent="04d47c4b31af7">
|
||||
<name>Scene Three</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>22</charCount>
|
||||
<wordCount>4</wordCount>
|
||||
<paraCount>1</paraCount>
|
||||
<cursorPos>2</cursorPos>
|
||||
</item>
|
||||
<item handle="19752e7f9d8af" order="3" parent="04d47c4b31af7">
|
||||
<name>Scene Four</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>20</charCount>
|
||||
<wordCount>4</wordCount>
|
||||
<paraCount>1</paraCount>
|
||||
<cursorPos>2</cursorPos>
|
||||
</item>
|
||||
<item handle="a764d5acf5a21" order="4" parent="04d47c4b31af7">
|
||||
<name>Scene Five</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>20</charCount>
|
||||
<wordCount>4</wordCount>
|
||||
<paraCount>1</paraCount>
|
||||
<cursorPos>2</cursorPos>
|
||||
</item>
|
||||
<item handle="a11282c943444" order="1" parent="None">
|
||||
<name>Characters</name>
|
||||
<type>ROOT</type>
|
||||
<class>CHARACTER</class>
|
||||
<status>New</status>
|
||||
<expanded>True</expanded>
|
||||
</item>
|
||||
<item handle="9058ae29f0dfd" order="0" parent="a11282c943444">
|
||||
<name>Protagonist</name>
|
||||
<type>FILE</type>
|
||||
<class>CHARACTER</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>11</charCount>
|
||||
<wordCount>1</wordCount>
|
||||
<paraCount>0</paraCount>
|
||||
<cursorPos>28</cursorPos>
|
||||
</item>
|
||||
<item handle="7ff63b8afc4cd" order="1" parent="a11282c943444">
|
||||
<name>Antagonist</name>
|
||||
<type>FILE</type>
|
||||
<class>CHARACTER</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>13</charCount>
|
||||
<wordCount>2</wordCount>
|
||||
<paraCount>1</paraCount>
|
||||
<cursorPos>26</cursorPos>
|
||||
</item>
|
||||
<item handle="dabb9d158b2b2" order="2" parent="None">
|
||||
<name>Plot</name>
|
||||
<type>ROOT</type>
|
||||
<class>PLOT</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
<item handle="4a817a3a84b42" order="3" parent="None">
|
||||
<name>World</name>
|
||||
<type>ROOT</type>
|
||||
<class>WORLD</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
</item>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
@@ -812,157 +812,6 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
|
||||
# END Test testCoreProject_OrphanedFiles
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreProject_OldFormat(mockGUI, nwOldProj):
|
||||
"""Test that a project folder structure of version 1.0 can be
|
||||
converted to the latest folder structure. Version 1.0 split the
|
||||
documents into 'data_0' ... 'data_f' folders, which are now all
|
||||
contained in a single 'content' folder.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
|
||||
# Create mock files for known legacy files
|
||||
deleteFiles = [
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.0"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.1"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.2"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.3"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.4"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.5"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.6"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.7"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.8"),
|
||||
os.path.join(nwOldProj, "cache", "nwProject.nwx.9"),
|
||||
os.path.join(nwOldProj, "meta", "mainOptions.json"),
|
||||
os.path.join(nwOldProj, "meta", "exportOptions.json"),
|
||||
os.path.join(nwOldProj, "meta", "outlineOptions.json"),
|
||||
os.path.join(nwOldProj, "meta", "timelineOptions.json"),
|
||||
os.path.join(nwOldProj, "meta", "docMergeOptions.json"),
|
||||
os.path.join(nwOldProj, "meta", "sessionLogOptions.json"),
|
||||
]
|
||||
|
||||
# Create mock files
|
||||
os.mkdir(os.path.join(nwOldProj, "cache"))
|
||||
for aFile in deleteFiles:
|
||||
writeFile(aFile, "Hi")
|
||||
for aFile in deleteFiles:
|
||||
assert os.path.isfile(aFile)
|
||||
|
||||
# Open project and check that files that are not supposed to be
|
||||
# there have been removed
|
||||
assert theProject.openProject(nwOldProj)
|
||||
for aFile in deleteFiles:
|
||||
assert not os.path.isfile(aFile)
|
||||
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_1"))
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_7"))
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_8"))
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_9"))
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_a"))
|
||||
assert not os.path.isdir(os.path.join(nwOldProj, "data_f"))
|
||||
|
||||
# Check that files we want to keep are in the right place
|
||||
assert os.path.isdir(os.path.join(nwOldProj, "cache"))
|
||||
assert os.path.isdir(os.path.join(nwOldProj, "content"))
|
||||
assert os.path.isdir(os.path.join(nwOldProj, "meta"))
|
||||
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "f528d831f5b24.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "88124a4292d8b.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "91239bf2f8b69.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "19752e7f9d8af.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "a764d5acf5a21.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "9058ae29f0dfd.nwd"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "content", "7ff63b8afc4cd.nwd"))
|
||||
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "meta", "tagsIndex.json"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "meta", "sessionInfo.log"))
|
||||
|
||||
# Close the project
|
||||
theProject.closeProject()
|
||||
|
||||
# Check that new files have been created
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "meta", "guiOptions.json"))
|
||||
assert os.path.isfile(os.path.join(nwOldProj, "ToC.txt"))
|
||||
|
||||
# END Test testCoreProject_OldFormat
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
|
||||
"""Test the functins that handle legacy data folders and structure
|
||||
with additional tests of failure handling.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
theProject.setProjectPath(fncDir)
|
||||
|
||||
# Check behaviour of deprecated files function on OSError
|
||||
tstFile = os.path.join(fncDir, "ToC.json")
|
||||
writeFile(tstFile, "stuff")
|
||||
assert os.path.isfile(tstFile)
|
||||
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("os.unlink", causeOSError)
|
||||
assert theProject._deprecatedFiles() is False
|
||||
|
||||
assert theProject._deprecatedFiles()
|
||||
assert not os.path.isfile(tstFile)
|
||||
|
||||
# Check processing non-folders
|
||||
tstFile = os.path.join(fncDir, "data_0")
|
||||
writeFile(tstFile, "stuff")
|
||||
assert os.path.isfile(tstFile)
|
||||
assert theProject._legacyDataFolder(tstFile) is False
|
||||
|
||||
# Check renaming/deleting of old document files
|
||||
tstData2 = os.path.join(fncDir, "data_2")
|
||||
tstData3 = os.path.join(fncDir, "data_3")
|
||||
tstDoc1m = os.path.join(tstData2, "000000000001_main.nwd")
|
||||
tstDoc1b = os.path.join(tstData2, "000000000001_main.bak")
|
||||
tstDoc2m = os.path.join(tstData2, "000000000002_main.nwd")
|
||||
tstDoc2b = os.path.join(tstData2, "000000000002_main.bak")
|
||||
tstDoc3m = os.path.join(tstData3, "tooshort003_main.nwd")
|
||||
tstDoc3b = os.path.join(tstData3, "tooshort003_main.bak")
|
||||
tstDir4a = os.path.join(tstData3, "stuff")
|
||||
|
||||
os.mkdir(tstData2)
|
||||
os.mkdir(tstData3)
|
||||
writeFile(tstDoc1m, "stuff")
|
||||
writeFile(tstDoc1b, "stuff")
|
||||
writeFile(tstDoc2m, "stuff")
|
||||
writeFile(tstDoc2b, "stuff")
|
||||
writeFile(tstDoc3m, "stuff")
|
||||
writeFile(tstDoc3b, "stuff")
|
||||
os.mkdir(tstDir4a)
|
||||
|
||||
# Make the above fail
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("os.rename", causeOSError)
|
||||
mp.setattr("os.unlink", causeOSError)
|
||||
with pytest.raises(OSError):
|
||||
theProject._legacyDataFolder(tstData2)
|
||||
theProject._legacyDataFolder(tstData3)
|
||||
assert os.path.isfile(tstDoc1m)
|
||||
assert os.path.isfile(tstDoc1b)
|
||||
assert os.path.isfile(tstDoc2m)
|
||||
assert os.path.isfile(tstDoc2b)
|
||||
assert os.path.isfile(tstDoc3m)
|
||||
assert os.path.isfile(tstDoc3b)
|
||||
|
||||
# And succeed ...
|
||||
assert theProject._legacyDataFolder(tstData2) is True
|
||||
assert theProject._legacyDataFolder(tstData3) is True
|
||||
|
||||
assert not os.path.isdir(tstData2)
|
||||
assert os.path.isdir(tstData3)
|
||||
assert os.path.isfile(os.path.join(fncDir, "content", "2000000000001.nwd"))
|
||||
assert os.path.isfile(os.path.join(fncDir, "content", "2000000000002.nwd"))
|
||||
assert os.path.isfile(os.path.join(fncDir, tstData3, "tooshort003_main.nwd"))
|
||||
assert os.path.isfile(os.path.join(fncDir, tstData3, "tooshort003_main.bak"))
|
||||
assert os.path.isdir(tstDir4a)
|
||||
|
||||
# END Test testCoreProject_LegacyData
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreProject_Backup(monkeypatch, mockGUI, fncDir, tmpDir):
|
||||
"""Test the automated backup feature of the project class. The test
|
||||
|
||||
Reference in New Issue
Block a user