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