Move lock file code to storage class

This commit is contained in:
Veronica Berglyd Olsen
2022-11-05 22:14:33 +01:00
parent 9cdf840d12
commit 82b34d9d2c
4 changed files with 73 additions and 138 deletions
+7 -71
View File
@@ -286,9 +286,9 @@ class NWProject(QObject):
# ============ # ============
if overrideLock: if overrideLock:
self._clearLockFile() self._storage.clearLockFile()
lockStatus = self._readLockFile() lockStatus = self._storage.readLockFile()
if len(lockStatus) > 0: if len(lockStatus) > 0:
if lockStatus[0] == "ERROR": if lockStatus[0] == "ERROR":
logger.warning("Failed to check lock file") logger.warning("Failed to check lock file")
@@ -310,7 +310,6 @@ class NWProject(QObject):
self._data = NWProjectData(self) self._data = NWProjectData(self)
projContent = [] projContent = []
xmlParsed = xmlReader.read(self._data, projContent) xmlParsed = xmlReader.read(self._data, projContent)
appVersion = xmlReader.appVersion or self.tr("Unknown") appVersion = xmlReader.appVersion or self.tr("Unknown")
@@ -397,7 +396,7 @@ class NWProject(QObject):
self._projOpened = time() self._projOpened = time()
self._projAltered = False self._projAltered = False
self._writeLockFile() self._storage.writeLockFile()
self.setProjectChanged(False) self.setProjectChanged(False)
self.mainGui.setStatus(self.tr("Opened Project: {0}").format(self._data.name)) self.mainGui.setStatus(self.tr("Opened Project: {0}").format(self._data.name))
@@ -458,7 +457,7 @@ class NWProject(QObject):
) )
self.mainConf.saveRecentCache() self.mainConf.saveRecentCache()
self._writeLockFile() self._storage.writeLockFile()
self.mainGui.setStatus(self.tr("Saved Project: {0}").format(self._data.name)) self.mainGui.setStatus(self.tr("Saved Project: {0}").format(self._data.name))
self.setProjectChanged(False) self.setProjectChanged(False)
@@ -471,7 +470,7 @@ class NWProject(QObject):
self._options.saveSettings() self._options.saveSettings()
self._tree.writeToCFile() self._tree.writeToCFile()
self._appendSessionStats(idleTime) self._appendSessionStats(idleTime)
self._clearLockFile() self._storage.clearLockFile()
self.clearProject() self.clearProject()
self.lockedBy = None self.lockedBy = None
return True return True
@@ -565,9 +564,9 @@ class NWProject(QObject):
baseName = os.path.join(baseDir, archName) baseName = os.path.join(baseDir, archName)
try: try:
self._clearLockFile() self._storage.clearLockFile()
shutil.make_archive(baseName, "zip", self.projPath, ".") shutil.make_archive(baseName, "zip", self.projPath, ".")
self._writeLockFile() self._storage.writeLockFile()
logger.info("Backup written to: %s", archName) logger.info("Backup written to: %s", archName)
if doNotify: if doNotify:
self.mainGui.makeAlert(self.tr( self.mainGui.makeAlert(self.tr(
@@ -819,69 +818,6 @@ class NWProject(QObject):
return True 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): def _checkFolder(self, thePath):
"""Check if a folder exists, and if it doesn't, create it. """Check if a folder exists, and if it doesn't, create it.
""" """
+1 -1
View File
@@ -276,7 +276,7 @@ class ProjectXMLReader:
projData.setSpellLang(xItem.text) projData.setSpellLang(xItem.text)
elif xItem.tag == "status": elif xItem.tag == "status":
self._parseStatusImport(xItem, projData.itemStatus) self._parseStatusImport(xItem, projData.itemStatus)
elif xItem.tag in ("import", "importance"): elif xItem.tag == "importance":
self._parseStatusImport(xItem, projData.itemImport) self._parseStatusImport(xItem, projData.itemImport)
elif xItem.tag == "lastHandle": elif xItem.tag == "lastHandle":
projData.setLastHandle(self._parseDictKeyText(xItem)) projData.setLastHandle(self._parseDictKeyText(xItem))
+62 -3
View File
@@ -24,11 +24,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import logging import logging
import novelwriter
from time import time
from pathlib import Path from pathlib import Path
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter
from novelwriter.error import logException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -41,10 +44,12 @@ class NWStorage:
def __init__(self, theProject): def __init__(self, theProject):
self.mainConf = novelwriter.CONFIG
self.theProject = theProject self.theProject = theProject
self._storagePath = None self._storagePath = None
self._runtimePath = None self._runtimePath = None
self._lockFilePath = None
self._openMode = self.MODE_INACTIVE self._openMode = self.MODE_INACTIVE
return return
@@ -104,6 +109,7 @@ class NWStorage:
self._storagePath = inPath self._storagePath = inPath
self._runtimePath = inPath self._runtimePath = inPath
self._lockFilePath = inPath / nwFiles.PROJ_LOCK
self._openMode = self.MODE_INPLACE self._openMode = self.MODE_INPLACE
if self._prepareStorage(checkLegacy=True) is False: if self._prepareStorage(checkLegacy=True) is False:
@@ -162,6 +168,62 @@ class NWStorage:
def getMetaFile(self, kind): def getMetaFile(self, kind):
pass 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 # Internal Functions
## ##
@@ -169,9 +231,6 @@ class NWStorage:
def _zipIt(self, target): def _zipIt(self, target):
pass pass
def _readLockFile(self):
pass
def _writeLockFile(self): def _writeLockFile(self):
pass pass
+3 -63
View File
@@ -196,12 +196,12 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncDir, mockRnd):
# Fail on lock file # Fail on lock file
theProject.setProjectPath(fncDir) theProject.setProjectPath(fncDir)
assert theProject._writeLockFile() assert theProject._storage.writeLockFile()
assert theProject.openProject(fncDir) is False assert theProject.openProject(fncDir) is False
# Fail to read lockfile (which still opens the project) # Fail to read lockfile (which still opens the project)
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError) mp.setattr("novelwriter.core.storage.NWStorage.readLockFile", lambda *a: ["ERROR"])
caplog.clear() caplog.clear()
assert theProject.openProject(fncDir) is True assert theProject.openProject(fncDir) is True
assert "Failed to check lock file" in caplog.text 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 # Force open with lockfile
theProject.setProjectPath(fncDir) theProject.setProjectPath(fncDir)
assert theProject._writeLockFile() assert theProject._storage.writeLockFile()
assert theProject.openProject(fncDir, overrideLock=True) is True assert theProject.openProject(fncDir, overrideLock=True) is True
assert theProject.closeProject() assert theProject.closeProject()
@@ -286,66 +286,6 @@ def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncDir, refDir):
# END Test testCoreProject_Save # 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 @pytest.mark.core
def testCoreProject_Helpers(monkeypatch, fncDir, mockGUI): def testCoreProject_Helpers(monkeypatch, fncDir, mockGUI):
"""Test helper functions for the project folder. """Test helper functions for the project folder.