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:
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.
"""
+1 -1
View File
@@ -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))
+62 -3
View File
@@ -24,11 +24,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
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
+3 -63
View File
@@ -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.