Move lock file code to storage class
This commit is contained in:
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user