From af410e0b15168c2399822f05008303413e0d8ed5 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Wed, 26 Feb 2020 22:09:37 +0100 Subject: [PATCH] Added functions to read and write project lockfile --- nw/config.py | 6 ++- nw/constants/constants.py | 1 + nw/project/project.py | 91 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 96 insertions(+), 2 deletions(-) diff --git a/nw/config.py b/nw/config.py index 3149edef..94c30f71 100644 --- a/nw/config.py +++ b/nw/config.py @@ -20,7 +20,7 @@ from os import path, mkdir, unlink, rename from datetime import datetime from PyQt5.Qt import PYQT_VERSION_STR -from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths +from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo from nw.constants import nwFiles, nwUnicode from nw.common import splitVersionNumber @@ -156,6 +156,10 @@ class Config: else: self.osUnknown = True + # Other System Info + self.hostName = QSysInfo.machineHostName() + self.kernelVer = QSysInfo.kernelVersion() + # Packages self.hasEnchant = False self.hasSymSpell = False diff --git a/nw/constants/constants.py b/nw/constants/constants.py index 4cc7f040..b39830bc 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -23,6 +23,7 @@ class nwFiles(): APP_ICON = "novelWriter.svg" PROJ_FILE = "nwProject.nwx" PROJ_DICT = "wordlist.txt" + PROJ_LOCK = "nwProject.lock" SESS_INFO = "sessionInfo.log" INDEX_FILE = "tagsIndex.json" OPTS_FILE = "guiOptions.json" diff --git a/nw/project/project.py b/nw/project/project.py index 887fe38c..afde1e02 100644 --- a/nw/project/project.py +++ b/nw/project/project.py @@ -40,6 +40,7 @@ class NWProject(): self.projOpened = None # The time stamp of when the project file was opened self.projChanged = None # The project has unsaved changes self.projAltered = None # The project has been altered this session + self.lockedBy = None # Data on which computer has the project open # Debug self.handleSeed = None @@ -178,7 +179,7 @@ class NWProject(): return - def openProject(self, fileName): + def openProject(self, fileName, overrideLock=False): """Open the project file provided, or if doesn't exist, assume it is a folder, and look for the file within it. If successful, parse the XML of the file and populate the project variables and @@ -201,6 +202,21 @@ class NWProject(): if not self._checkFolder(self.projMeta): return + if overrideLock: + self._clearLockFile() + + lockStatus = self._readLockFile() + if len(lockStatus) > 0: + if lockStatus[0] == "ERROR": + logger.warning("Failed to check lock file") + else: + logger.error("Project is locked, so not opening") + self.lockedBy = lockStatus + self.clearProject() + return False + else: + logger.verbose("Project is not locked") + try: projectMaintenance(self) except Exception as E: @@ -308,6 +324,7 @@ class NWProject(): self.setProjectChanged(False) self.projOpened = time() self.projAltered = False + self._writeLockFile() return True @@ -398,14 +415,19 @@ class NWProject(): self.mainConf.updateRecentCache(self.projPath, self.projName, self.currWCount, saveTime) self.mainConf.saveRecentCache() + self._writeLockFile() self.theParent.setStatus("Saved Project: %s" % self.projName) self.setProjectChanged(False) return True def closeProject(self): + """Close the current project and clear all meta data. + """ self._appendSessionStats() + self._clearLockFile() self.clearProject() + self.lockedBy = None return True ## @@ -645,6 +667,73 @@ class NWProject(): # Internal Functions ## + def _readLockFile(self): + """Reads the lock file in the project folder. + """ + + if self.projPath is None: + return ["ERROR"] + + lockFile = path.join(self.projPath, nwFiles.PROJ_LOCK) + if not path.isfile(lockFile): + return [] + + try: + with open(lockFile, mode="r", encoding="utf8") as inFile: + theData = inFile.read() + theLines = theData.splitlines() + if len(theLines) == 4: + return theLines + else: + return ["ERROR"] + + except Exception as e: + logger.error("Failed to read project lockfile") + logger.error(str(e)) + return ["ERROR"] + + return ["ERROR"] + + def _writeLockFile(self): + """Writes a lock file to the project folder. + """ + + if self.projPath is None: + return False + + lockFile = path.join(self.projPath, nwFiles.PROJ_LOCK) + try: + with open(lockFile, mode="w+", encoding="utf8") 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 as e: + logger.error("Failed to write project lockfile") + logger.error(str(e)) + return False + + return True + + def _clearLockFile(self): + """Remove the lock file, if it exists. + """ + if self.projPath is None: + return False + + lockFile = path.join(self.projPath, nwFiles.PROJ_LOCK) + if path.isfile(lockFile): + try: + unlink(lockFile) + return True + except Exception as e: + logger.error("Failed to remove project lockfile") + logger.error(str(e)) + return False + + return None + def _checkFolder(self, thePath): if not path.isdir(thePath): try: