From 82b34d9d2cdba03fbcbce69290073d03fd5df32f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 5 Nov 2022 22:14:33 +0100 Subject: [PATCH] Move lock file code to storage class --- novelwriter/core/project.py | 78 +++------------------------- novelwriter/core/projectxml.py | 2 +- novelwriter/core/storage.py | 65 +++++++++++++++++++++-- tests/test_core/test_core_project.py | 66 ++--------------------- 4 files changed, 73 insertions(+), 138 deletions(-) diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 7892d16e..9d0c78ca 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -286,9 +286,9 @@ class NWProject(QObject): # ============ if overrideLock: - self._clearLockFile() + self._storage.clearLockFile() - lockStatus = self._readLockFile() + lockStatus = self._storage.readLockFile() if len(lockStatus) > 0: if lockStatus[0] == "ERROR": logger.warning("Failed to check lock file") @@ -310,7 +310,6 @@ class NWProject(QObject): self._data = NWProjectData(self) projContent = [] - xmlParsed = xmlReader.read(self._data, projContent) appVersion = xmlReader.appVersion or self.tr("Unknown") @@ -397,7 +396,7 @@ class NWProject(QObject): self._projOpened = time() self._projAltered = False - self._writeLockFile() + self._storage.writeLockFile() self.setProjectChanged(False) self.mainGui.setStatus(self.tr("Opened Project: {0}").format(self._data.name)) @@ -458,7 +457,7 @@ class NWProject(QObject): ) self.mainConf.saveRecentCache() - self._writeLockFile() + self._storage.writeLockFile() self.mainGui.setStatus(self.tr("Saved Project: {0}").format(self._data.name)) self.setProjectChanged(False) @@ -471,7 +470,7 @@ class NWProject(QObject): self._options.saveSettings() self._tree.writeToCFile() self._appendSessionStats(idleTime) - self._clearLockFile() + self._storage.clearLockFile() self.clearProject() self.lockedBy = None return True @@ -565,9 +564,9 @@ class NWProject(QObject): baseName = os.path.join(baseDir, archName) try: - self._clearLockFile() + self._storage.clearLockFile() shutil.make_archive(baseName, "zip", self.projPath, ".") - self._writeLockFile() + self._storage.writeLockFile() logger.info("Backup written to: %s", archName) if doNotify: self.mainGui.makeAlert(self.tr( @@ -819,69 +818,6 @@ class NWProject(QObject): return True - def _readLockFile(self): - """Reads the lock file in the project folder. - """ - if self.projPath is None: - return ["ERROR"] - - lockFile = os.path.join(self.projPath, nwFiles.PROJ_LOCK) - if not os.path.isfile(lockFile): - return [] - - theLines = [] - try: - with open(lockFile, mode="r", encoding="utf-8") as inFile: - theData = inFile.read() - theLines = theData.splitlines() - if len(theLines) != 4: - return ["ERROR"] - - except Exception: - logger.error("Failed to read project lockfile") - logException() - return ["ERROR"] - - return theLines - - def _writeLockFile(self): - """Writes a lock file to the project folder. - """ - if self.projPath is None: - return False - - lockFile = os.path.join(self.projPath, nwFiles.PROJ_LOCK) - try: - with open(lockFile, mode="w+", encoding="utf-8") as outFile: - outFile.write("%s\n" % self.mainConf.hostName) - outFile.write("%s\n" % self.mainConf.osType) - outFile.write("%s\n" % self.mainConf.kernelVer) - outFile.write("%d\n" % time()) - - except Exception: - logger.error("Failed to write project lockfile") - logException() - return False - - return True - - def _clearLockFile(self): - """Remove the lock file, if it exists. - """ - if self.projPath is None: - return False - - lockFile = os.path.join(self.projPath, nwFiles.PROJ_LOCK) - if os.path.isfile(lockFile): - try: - os.unlink(lockFile) - except Exception: - logger.error("Failed to remove project lockfile") - logException() - return False - - return True - def _checkFolder(self, thePath): """Check if a folder exists, and if it doesn't, create it. """ diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py index e8dd1621..618fb30a 100644 --- a/novelwriter/core/projectxml.py +++ b/novelwriter/core/projectxml.py @@ -276,7 +276,7 @@ class ProjectXMLReader: projData.setSpellLang(xItem.text) elif xItem.tag == "status": self._parseStatusImport(xItem, projData.itemStatus) - elif xItem.tag in ("import", "importance"): + elif xItem.tag == "importance": self._parseStatusImport(xItem, projData.itemImport) elif xItem.tag == "lastHandle": projData.setLastHandle(self._parseDictKeyText(xItem)) diff --git a/novelwriter/core/storage.py b/novelwriter/core/storage.py index dfa4a2a2..0813f7cc 100644 --- a/novelwriter/core/storage.py +++ b/novelwriter/core/storage.py @@ -24,11 +24,14 @@ along with this program. If not, see . """ import logging +import novelwriter +from time import time from pathlib import Path from novelwriter.constants import nwFiles from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter +from novelwriter.error import logException logger = logging.getLogger(__name__) @@ -41,10 +44,12 @@ class NWStorage: def __init__(self, theProject): + self.mainConf = novelwriter.CONFIG self.theProject = theProject self._storagePath = None self._runtimePath = None + self._lockFilePath = None self._openMode = self.MODE_INACTIVE return @@ -104,6 +109,7 @@ class NWStorage: self._storagePath = inPath self._runtimePath = inPath + self._lockFilePath = inPath / nwFiles.PROJ_LOCK self._openMode = self.MODE_INPLACE if self._prepareStorage(checkLegacy=True) is False: @@ -162,6 +168,62 @@ class NWStorage: def getMetaFile(self, kind): pass + def readLockFile(self): + """Read the project lock file. + """ + if self._lockFilePath is None: + return ["ERROR"] + + if not self._lockFilePath.exists(): + return [] + + try: + lines = self._lockFilePath.read_text(encoding="utf-8").split(";") + except Exception: + logger.error("Failed to read project lockfile") + logException() + return ["ERROR"] + + if len(lines) != 4: + return ["ERROR"] + + return lines + + def writeLockFile(self): + """Write the project lock file. + """ + if self._lockFilePath is None: + return False + + data = [ + self.mainConf.hostName, self.mainConf.osType, + self.mainConf.kernelVer, str(int(time())) + ] + try: + self._lockFilePath.write_text(";".join(data), encoding="utf-8") + except Exception: + logger.error("Failed to write project lockfile") + logException() + return False + + return True + + def clearLockFile(self): + """Remove the lock file, if it exists. + """ + if self._lockFilePath is None: + return False + + if self._lockFilePath.exists(): + try: + self._lockFilePath.unlink() + except Exception: + logger.error("Failed to remove project lockfile") + logException() + return False + + return True + ## # Internal Functions ## @@ -169,9 +231,6 @@ class NWStorage: def _zipIt(self, target): pass - def _readLockFile(self): - pass - def _writeLockFile(self): pass diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py index 118c0cb7..752e216b 100644 --- a/tests/test_core/test_core_project.py +++ b/tests/test_core/test_core_project.py @@ -196,12 +196,12 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncDir, mockRnd): # Fail on lock file theProject.setProjectPath(fncDir) - assert theProject._writeLockFile() + assert theProject._storage.writeLockFile() assert theProject.openProject(fncDir) is False # Fail to read lockfile (which still opens the project) with monkeypatch.context() as mp: - mp.setattr("builtins.open", causeOSError) + mp.setattr("novelwriter.core.storage.NWStorage.readLockFile", lambda *a: ["ERROR"]) caplog.clear() assert theProject.openProject(fncDir) is True assert "Failed to check lock file" in caplog.text @@ -209,7 +209,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncDir, mockRnd): # Force open with lockfile theProject.setProjectPath(fncDir) - assert theProject._writeLockFile() + assert theProject._storage.writeLockFile() assert theProject.openProject(fncDir, overrideLock=True) is True assert theProject.closeProject() @@ -286,66 +286,6 @@ def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncDir, refDir): # END Test testCoreProject_Save -@pytest.mark.core -def testCoreProject_LockFile(monkeypatch, fncDir, mockGUI): - """Test lock file functions for the project folder. - """ - theProject = NWProject(mockGUI) - - lockFile = os.path.join(fncDir, nwFiles.PROJ_LOCK) - - # No project - assert theProject._writeLockFile() is False - assert theProject._readLockFile() == ["ERROR"] - assert theProject._clearLockFile() is False - - theProject.projPath = fncDir - theProject.mainConf.hostName = "TestHost" - theProject.mainConf.osType = "TestOS" - theProject.mainConf.kernelVer = "1.0" - - # Block open - with monkeypatch.context() as mp: - mp.setattr("builtins.open", causeOSError) - assert theProject._writeLockFile() is False - - # Write lock file - with monkeypatch.context() as mp: - mp.setattr("novelwriter.core.project.time", lambda: 123.4) - assert theProject._writeLockFile() is True - assert readFile(lockFile) == "TestHost\nTestOS\n1.0\n123\n" - - # Block open - with monkeypatch.context() as mp: - mp.setattr("builtins.open", causeOSError) - assert theProject._readLockFile() == ["ERROR"] - - # Read lock file - assert theProject._readLockFile() == ["TestHost", "TestOS", "1.0", "123"] - - # Block unlink - with monkeypatch.context() as mp: - mp.setattr("os.unlink", causeOSError) - assert os.path.isfile(lockFile) - assert theProject._clearLockFile() is False - assert os.path.isfile(lockFile) - - # Clear file - assert os.path.isfile(lockFile) - assert theProject._clearLockFile() is True - assert not os.path.isfile(lockFile) - - # Read again, no file - assert theProject._readLockFile() == [] - - # Read an invalid lock file - writeFile(lockFile, "A\nB") - assert theProject._readLockFile() == ["ERROR"] - assert theProject._clearLockFile() is True - -# END Test testCoreProject_LockFile - - @pytest.mark.core def testCoreProject_Helpers(monkeypatch, fncDir, mockGUI): """Test helper functions for the project folder.