Move recent cache out of config, and update all tests
This commit is contained in:
+114
-92
@@ -74,8 +74,8 @@ class Config:
|
|||||||
self._appPath = self._appRoot
|
self._appPath = self._appRoot
|
||||||
|
|
||||||
# Runtime Settings and Variables
|
# Runtime Settings and Variables
|
||||||
self.hasError = False # True if the config class encountered an error
|
self._hasError = False # True if the config class encountered an error
|
||||||
self.errData = [] # List of error messages
|
self._errData = [] # List of error messages
|
||||||
self.confChanged = False # True whenever the config has chenged, false after save
|
self.confChanged = False # True whenever the config has chenged, false after save
|
||||||
self.cmdOpen = None # Path from command line for project to be opened on launch
|
self.cmdOpen = None # Path from command line for project to be opened on launch
|
||||||
|
|
||||||
@@ -92,6 +92,8 @@ class Config:
|
|||||||
# User Settings
|
# User Settings
|
||||||
# =============
|
# =============
|
||||||
|
|
||||||
|
self._recentProj = RecentProjects(self._dataPath)
|
||||||
|
|
||||||
# General GUI Settings
|
# General GUI Settings
|
||||||
self.guiLang = self._qLocal.name()
|
self.guiLang = self._qLocal.name()
|
||||||
self.guiTheme = "" # GUI theme
|
self.guiTheme = "" # GUI theme
|
||||||
@@ -245,6 +247,18 @@ class Config:
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Properties
|
||||||
|
##
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hasError(self):
|
||||||
|
return self._hasError
|
||||||
|
|
||||||
|
@property
|
||||||
|
def recentProjects(self):
|
||||||
|
return self._recentProj
|
||||||
|
|
||||||
##
|
##
|
||||||
# Methods
|
# Methods
|
||||||
##
|
##
|
||||||
@@ -282,6 +296,15 @@ class Config:
|
|||||||
return self._lastPath
|
return self._lastPath
|
||||||
return Path.home().absolute()
|
return Path.home().absolute()
|
||||||
|
|
||||||
|
def errorText(self):
|
||||||
|
"""Compile and return error messages from the initialisation of
|
||||||
|
the Config class, and clear the error buffer.
|
||||||
|
"""
|
||||||
|
errMessage = "<br>".join(self._errData)
|
||||||
|
self._hasError = False
|
||||||
|
self._errData = []
|
||||||
|
return errMessage
|
||||||
|
|
||||||
##
|
##
|
||||||
# Config Actions
|
# Config Actions
|
||||||
##
|
##
|
||||||
@@ -321,12 +344,12 @@ class Config:
|
|||||||
else:
|
else:
|
||||||
self.saveConfig()
|
self.saveConfig()
|
||||||
|
|
||||||
self.loadRecentCache()
|
self._recentProj.loadCache()
|
||||||
self._checkOptionalPackages()
|
self._checkOptionalPackages()
|
||||||
|
|
||||||
logger.debug("Config initialisation complete")
|
logger.debug("Config initialisation complete")
|
||||||
|
|
||||||
return True
|
return
|
||||||
|
|
||||||
def initLocalisation(self, nwApp):
|
def initLocalisation(self, nwApp):
|
||||||
"""Initialise the localisation of the GUI.
|
"""Initialise the localisation of the GUI.
|
||||||
@@ -345,7 +368,7 @@ class Config:
|
|||||||
qTrans = QTranslator()
|
qTrans = QTranslator()
|
||||||
lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_"))
|
lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_"))
|
||||||
if lngFile not in self._qtTrans:
|
if lngFile not in self._qtTrans:
|
||||||
if qTrans.load(lngFile, lngPath):
|
if qTrans.load(lngFile, str(lngPath)):
|
||||||
logger.debug("Loaded: %s", os.path.join(lngPath, lngFile))
|
logger.debug("Loaded: %s", os.path.join(lngPath, lngFile))
|
||||||
nwApp.installTranslator(qTrans)
|
nwApp.installTranslator(qTrans)
|
||||||
self._qtTrans[lngFile] = qTrans
|
self._qtTrans[lngFile] = qTrans
|
||||||
@@ -367,12 +390,12 @@ class Config:
|
|||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
for qmFile in os.listdir(self._nwLangPath):
|
for qmFile in Path(self._nwLangPath).iterdir():
|
||||||
if not os.path.isfile(os.path.join(self._nwLangPath, qmFile)):
|
qmName = qmFile.name
|
||||||
|
if not (qmFile.is_file() and qmName.startswith(fPre) and qmName.endswith(fExt)):
|
||||||
continue
|
continue
|
||||||
if not qmFile.startswith(fPre) or not qmFile.endswith(fExt):
|
|
||||||
continue
|
qmLang = qmName[len(fPre):-len(fExt)]
|
||||||
qmLang = qmFile[len(fPre):-len(fExt)]
|
|
||||||
qmName = QLocale(qmLang).nativeLanguageName().title()
|
qmName = QLocale(qmLang).nativeLanguageName().title()
|
||||||
if qmLang and qmName and qmLang != "en_GB":
|
if qmLang and qmName and qmLang != "en_GB":
|
||||||
langList[qmLang] = qmName
|
langList[qmLang] = qmName
|
||||||
@@ -392,9 +415,9 @@ class Config:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("Could not load config file")
|
logger.error("Could not load config file")
|
||||||
logException()
|
logException()
|
||||||
self.hasError = True
|
self._hasError = True
|
||||||
self.errData.append("Could not load config file")
|
self._errData.append("Could not load config file")
|
||||||
self.errData.append(formatException(exc))
|
self._errData.append(formatException(exc))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
@@ -606,80 +629,13 @@ class Config:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("Could not save config file")
|
logger.error("Could not save config file")
|
||||||
logException()
|
logException()
|
||||||
self.hasError = True
|
self._hasError = True
|
||||||
self.errData.append("Could not save config file")
|
self._errData.append("Could not save config file")
|
||||||
self.errData.append(formatException(exc))
|
self._errData.append(formatException(exc))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def loadRecentCache(self):
|
|
||||||
"""Load the cache file for recent projects.
|
|
||||||
"""
|
|
||||||
self.recentProj = {}
|
|
||||||
|
|
||||||
cacheFile = self._dataPath / nwFiles.RECENT_FILE
|
|
||||||
if not os.path.isfile(cacheFile):
|
|
||||||
return True
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(cacheFile, mode="r", encoding="utf-8") as inFile:
|
|
||||||
theData = json.load(inFile)
|
|
||||||
|
|
||||||
for projPath, theEntry in theData.items():
|
|
||||||
self.recentProj[projPath] = {
|
|
||||||
"title": theEntry.get("title", ""),
|
|
||||||
"time": theEntry.get("time", 0),
|
|
||||||
"words": theEntry.get("words", 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
self.hasError = True
|
|
||||||
self.errData.append("Could not load recent project cache")
|
|
||||||
self.errData.append(formatException(exc))
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def saveRecentCache(self):
|
|
||||||
"""Save the cache dictionary of recent projects.
|
|
||||||
"""
|
|
||||||
cacheFile = self._dataPath / nwFiles.RECENT_FILE
|
|
||||||
cacheTemp = cacheFile.with_suffix(".tmp")
|
|
||||||
try:
|
|
||||||
with open(cacheTemp, mode="w+", encoding="utf-8") as outFile:
|
|
||||||
json.dump(self.recentProj, outFile, indent=2)
|
|
||||||
cacheTemp.replace(cacheFile)
|
|
||||||
except Exception as exc:
|
|
||||||
self.hasError = True
|
|
||||||
self.errData.append("Could not save recent project cache")
|
|
||||||
self.errData.append(formatException(exc))
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def updateRecentCache(self, projPath, projTitle, wordCount, saveTime):
|
|
||||||
"""Add or update recent cache information on a given project.
|
|
||||||
"""
|
|
||||||
self.recentProj[os.path.abspath(projPath)] = {
|
|
||||||
"title": projTitle,
|
|
||||||
"time": int(saveTime),
|
|
||||||
"words": int(wordCount),
|
|
||||||
}
|
|
||||||
return True
|
|
||||||
|
|
||||||
def removeFromRecentCache(self, thePath):
|
|
||||||
"""Trying to remove a path from the recent projects cache.
|
|
||||||
"""
|
|
||||||
if thePath in self.recentProj:
|
|
||||||
del self.recentProj[thePath]
|
|
||||||
logger.debug("Removed recent: %s", thePath)
|
|
||||||
self.saveRecentCache()
|
|
||||||
else:
|
|
||||||
logger.error("Unknown recent: %s", thePath)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Setters
|
# Setters
|
||||||
##
|
##
|
||||||
@@ -828,15 +784,6 @@ class Config:
|
|||||||
def getTabWidth(self):
|
def getTabWidth(self):
|
||||||
return self.pxInt(max(self.tabWidth, 0))
|
return self.pxInt(max(self.tabWidth, 0))
|
||||||
|
|
||||||
def getErrData(self):
|
|
||||||
"""Compile and return error messages from the initialisation of
|
|
||||||
the Config class, and clear the error buffer.
|
|
||||||
"""
|
|
||||||
errMessage = "<br>".join(self.errData)
|
|
||||||
self.hasError = False
|
|
||||||
self.errData = []
|
|
||||||
return errMessage
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
@@ -872,3 +819,78 @@ class Config:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# END Class Config
|
# END Class Config
|
||||||
|
|
||||||
|
|
||||||
|
class RecentProjects:
|
||||||
|
|
||||||
|
def __init__(self, dataPath):
|
||||||
|
self._dataPath = dataPath
|
||||||
|
self._data = {}
|
||||||
|
return
|
||||||
|
|
||||||
|
def loadCache(self):
|
||||||
|
"""Load the cache file for recent projects.
|
||||||
|
"""
|
||||||
|
self._data = {}
|
||||||
|
|
||||||
|
cacheFile = self._dataPath / nwFiles.RECENT_FILE
|
||||||
|
if not cacheFile.is_file():
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(cacheFile, mode="r", encoding="utf-8") as inFile:
|
||||||
|
theData = json.load(inFile)
|
||||||
|
for projPath, theEntry in theData.items():
|
||||||
|
self._data[projPath] = {
|
||||||
|
"title": theEntry.get("title", ""),
|
||||||
|
"words": theEntry.get("words", 0),
|
||||||
|
"time": theEntry.get("time", 0),
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
logger.error("Could not load recent project cache")
|
||||||
|
logException()
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def saveCache(self):
|
||||||
|
"""Save the cache dictionary of recent projects.
|
||||||
|
"""
|
||||||
|
cacheFile = self._dataPath / nwFiles.RECENT_FILE
|
||||||
|
cacheTemp = cacheFile.with_suffix(".tmp")
|
||||||
|
try:
|
||||||
|
with open(cacheTemp, mode="w+", encoding="utf-8") as outFile:
|
||||||
|
json.dump(self._data, outFile, indent=2)
|
||||||
|
cacheTemp.replace(cacheFile)
|
||||||
|
except Exception:
|
||||||
|
logger.error("Could not save recent project cache")
|
||||||
|
logException()
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def listEntries(self):
|
||||||
|
"""List all items in the cache.
|
||||||
|
"""
|
||||||
|
return [(k, e["title"], e["words"], e["time"]) for k, e in self._data.items()]
|
||||||
|
|
||||||
|
def update(self, projPath, projTitle, wordCount, saveTime):
|
||||||
|
"""Add or update recent cache information on a given project.
|
||||||
|
"""
|
||||||
|
self._data[str(projPath)] = {
|
||||||
|
"title": projTitle,
|
||||||
|
"words": int(wordCount),
|
||||||
|
"time": int(saveTime),
|
||||||
|
}
|
||||||
|
self.saveCache()
|
||||||
|
return
|
||||||
|
|
||||||
|
def remove(self, projPath):
|
||||||
|
"""Try to remove a path from the recent projects cache.
|
||||||
|
"""
|
||||||
|
if self._data.pop(str(projPath), None) is not None:
|
||||||
|
logger.debug("Removed recent: %s", projPath)
|
||||||
|
self.saveCache()
|
||||||
|
return
|
||||||
|
|
||||||
|
# END Class RecentProjects
|
||||||
|
|||||||
@@ -354,10 +354,9 @@ class NWProject(QObject):
|
|||||||
self._loadProjectLocalisation()
|
self._loadProjectLocalisation()
|
||||||
|
|
||||||
# Update recent projects
|
# Update recent projects
|
||||||
self.mainConf.updateRecentCache(
|
self.mainConf.recentProjects.update(
|
||||||
self._storage.storagePath, self._data.name, sum(self._data.initCounts), time()
|
self._storage.storagePath, self._data.name, sum(self._data.initCounts), time()
|
||||||
)
|
)
|
||||||
self.mainConf.saveRecentCache()
|
|
||||||
|
|
||||||
# Check the project tree consistency
|
# Check the project tree consistency
|
||||||
for tItem in self._tree:
|
for tItem in self._tree:
|
||||||
@@ -424,10 +423,9 @@ class NWProject(QObject):
|
|||||||
self._storage.runPostSaveTasks(autoSave=autoSave)
|
self._storage.runPostSaveTasks(autoSave=autoSave)
|
||||||
|
|
||||||
# Update recent projects
|
# Update recent projects
|
||||||
self.mainConf.updateRecentCache(
|
self.mainConf.recentProjects.update(
|
||||||
self._storage.storagePath, self._data.name, sum(self._data.currCounts), saveTime
|
self._storage.storagePath, self._data.name, sum(self._data.currCounts), saveTime
|
||||||
)
|
)
|
||||||
self.mainConf.saveRecentCache()
|
|
||||||
|
|
||||||
self._storage.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))
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ class GuiProjectLoad(QDialog):
|
|||||||
).format(projName)
|
).format(projName)
|
||||||
)
|
)
|
||||||
if msgYes:
|
if msgYes:
|
||||||
self.mainConf.removeFromRecentCache(
|
self.mainConf.recentProjects.remove(
|
||||||
selList[0].data(self.C_NAME, Qt.UserRole)
|
selList[0].data(self.C_NAME, Qt.UserRole)
|
||||||
)
|
)
|
||||||
self._populateList()
|
self._populateList()
|
||||||
@@ -264,23 +264,17 @@ class GuiProjectLoad(QDialog):
|
|||||||
def _populateList(self):
|
def _populateList(self):
|
||||||
"""Populate the list box with recent project data.
|
"""Populate the list box with recent project data.
|
||||||
"""
|
"""
|
||||||
dataList = []
|
|
||||||
for projPath in self.mainConf.recentProj:
|
|
||||||
theEntry = self.mainConf.recentProj[projPath]
|
|
||||||
theTitle = theEntry.get("title", "")
|
|
||||||
theTime = theEntry.get("time", 0)
|
|
||||||
theWords = theEntry.get("words", 0)
|
|
||||||
dataList.append([theTitle, theTime, theWords, projPath])
|
|
||||||
|
|
||||||
self.listBox.clear()
|
self.listBox.clear()
|
||||||
sortList = sorted(dataList, key=lambda x: x[1], reverse=True)
|
dataList = self.mainConf.recentProjects.listEntries()
|
||||||
for theTitle, theTime, theWords, projPath in sortList:
|
sortList = sorted(dataList, key=lambda x: x[3], reverse=True)
|
||||||
|
nwxIcon = self.mainGui.mainTheme.getIcon("proj_nwx")
|
||||||
|
for path, title, words, time in sortList:
|
||||||
newItem = QTreeWidgetItem([""]*4)
|
newItem = QTreeWidgetItem([""]*4)
|
||||||
newItem.setIcon(self.C_NAME, self.mainGui.mainTheme.getIcon("proj_nwx"))
|
newItem.setIcon(self.C_NAME, nwxIcon)
|
||||||
newItem.setText(self.C_NAME, theTitle)
|
newItem.setText(self.C_NAME, title)
|
||||||
newItem.setData(self.C_NAME, Qt.UserRole, projPath)
|
newItem.setData(self.C_NAME, Qt.UserRole, path)
|
||||||
newItem.setText(self.C_COUNT, formatInt(theWords))
|
newItem.setText(self.C_COUNT, formatInt(words))
|
||||||
newItem.setText(self.C_TIME, datetime.fromtimestamp(theTime).strftime("%x %X"))
|
newItem.setText(self.C_TIME, datetime.fromtimestamp(time).strftime("%x %X"))
|
||||||
newItem.setTextAlignment(self.C_NAME, Qt.AlignLeft | Qt.AlignVCenter)
|
newItem.setTextAlignment(self.C_NAME, Qt.AlignLeft | Qt.AlignVCenter)
|
||||||
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight | Qt.AlignVCenter)
|
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight | Qt.AlignVCenter)
|
||||||
newItem.setTextAlignment(self.C_TIME, Qt.AlignRight | Qt.AlignVCenter)
|
newItem.setTextAlignment(self.C_TIME, Qt.AlignRight | Qt.AlignVCenter)
|
||||||
|
|||||||
@@ -1144,7 +1144,7 @@ class GuiMain(QMainWindow):
|
|||||||
errors since it is initialised before the GUI itself.
|
errors since it is initialised before the GUI itself.
|
||||||
"""
|
"""
|
||||||
if self.mainConf.hasError:
|
if self.mainConf.hasError:
|
||||||
self.makeAlert(self.mainConf.getErrData(), nwAlert.ERROR)
|
self.makeAlert(self.mainConf.errorText(), nwAlert.ERROR)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
+186
-192
@@ -19,16 +19,16 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from mock import causeOSError, MockApp
|
from mock import causeOSError, MockApp
|
||||||
from tools import cmpFiles, writeFile
|
from tools import cmpFiles, writeFile
|
||||||
|
|
||||||
from novelwriter.config import Config
|
from novelwriter.config import Config, RecentProjects
|
||||||
from novelwriter.constants import nwFiles
|
from novelwriter.constants import nwFiles
|
||||||
|
|
||||||
|
|
||||||
@@ -37,173 +37,140 @@ def testBaseConfig_Constructor(monkeypatch):
|
|||||||
"""Test config contructor.
|
"""Test config contructor.
|
||||||
"""
|
"""
|
||||||
# Linux
|
# Linux
|
||||||
monkeypatch.setattr("sys.platform", "linux")
|
with monkeypatch.context() as mp:
|
||||||
tstConf = Config()
|
mp.setattr("sys.platform", "linux")
|
||||||
assert tstConf.osLinux is True
|
tstConf = Config()
|
||||||
assert tstConf.osDarwin is False
|
assert tstConf.osLinux is True
|
||||||
assert tstConf.osWindows is False
|
assert tstConf.osDarwin is False
|
||||||
assert tstConf.osUnknown is False
|
assert tstConf.osWindows is False
|
||||||
|
assert tstConf.osUnknown is False
|
||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
monkeypatch.setattr("sys.platform", "darwin")
|
with monkeypatch.context() as mp:
|
||||||
tstConf = Config()
|
mp.setattr("sys.platform", "darwin")
|
||||||
assert tstConf.osLinux is False
|
tstConf = Config()
|
||||||
assert tstConf.osDarwin is True
|
assert tstConf.osLinux is False
|
||||||
assert tstConf.osWindows is False
|
assert tstConf.osDarwin is True
|
||||||
assert tstConf.osUnknown is False
|
assert tstConf.osWindows is False
|
||||||
|
assert tstConf.osUnknown is False
|
||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
monkeypatch.setattr("sys.platform", "win32")
|
with monkeypatch.context() as mp:
|
||||||
tstConf = Config()
|
mp.setattr("sys.platform", "win32")
|
||||||
assert tstConf.osLinux is False
|
tstConf = Config()
|
||||||
assert tstConf.osDarwin is False
|
assert tstConf.osLinux is False
|
||||||
assert tstConf.osWindows is True
|
assert tstConf.osDarwin is False
|
||||||
assert tstConf.osUnknown is False
|
assert tstConf.osWindows is True
|
||||||
|
assert tstConf.osUnknown is False
|
||||||
|
|
||||||
# Cygwin
|
# Cygwin
|
||||||
monkeypatch.setattr("sys.platform", "cygwin")
|
with monkeypatch.context() as mp:
|
||||||
tstConf = Config()
|
mp.setattr("sys.platform", "cygwin")
|
||||||
assert tstConf.osLinux is False
|
tstConf = Config()
|
||||||
assert tstConf.osDarwin is False
|
assert tstConf.osLinux is False
|
||||||
assert tstConf.osWindows is True
|
assert tstConf.osDarwin is False
|
||||||
assert tstConf.osUnknown is False
|
assert tstConf.osWindows is True
|
||||||
|
assert tstConf.osUnknown is False
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
monkeypatch.setattr("sys.platform", "some_other_os")
|
with monkeypatch.context() as mp:
|
||||||
tstConf = Config()
|
mp.setattr("sys.platform", "some_other_os")
|
||||||
assert tstConf.osLinux is False
|
tstConf = Config()
|
||||||
assert tstConf.osDarwin is False
|
assert tstConf.osLinux is False
|
||||||
assert tstConf.osWindows is False
|
assert tstConf.osDarwin is False
|
||||||
assert tstConf.osUnknown is True
|
assert tstConf.osWindows is False
|
||||||
|
assert tstConf.osUnknown is True
|
||||||
|
|
||||||
|
# App is single file
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("pathlib.Path.is_file", lambda *a: True)
|
||||||
|
tstConf = Config()
|
||||||
|
assert tstConf._appPath == tstConf._appRoot
|
||||||
|
|
||||||
# END Test testBaseConfig_Constructor
|
# END Test testBaseConfig_Constructor
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.base
|
@pytest.mark.base
|
||||||
@pytest.mark.skip
|
def testBaseConfig_InitLoadSave(monkeypatch, fncPath, tstPaths):
|
||||||
def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
|
|
||||||
"""Test config intialisation.
|
"""Test config intialisation.
|
||||||
"""
|
"""
|
||||||
tstConf = Config()
|
tstConf = Config()
|
||||||
|
|
||||||
confFile = os.path.join(tmpDir, "novelwriter.conf")
|
confFile = fncPath / nwFiles.CONF_FILE
|
||||||
testFile = os.path.join(outDir, "baseConfig_novelwriter.conf")
|
testFile = tstPaths.outDir / "baseConfig_novelwriter.conf"
|
||||||
compFile = os.path.join(refDir, "baseConfig_novelwriter.conf")
|
compFile = tstPaths.refDir / "baseConfig_novelwriter.conf"
|
||||||
|
|
||||||
# Make sure we don't have any old conf file
|
# Make sure we don't have any old conf file
|
||||||
if os.path.isfile(confFile):
|
if confFile.is_file():
|
||||||
os.unlink(confFile)
|
confFile.unlink()
|
||||||
|
|
||||||
# Let the config class figure out the path
|
# Running init against a new oath should write a new config file
|
||||||
with monkeypatch.context() as mp:
|
tstConf.initConfig(confPath=fncPath, dataPath=fncPath)
|
||||||
mp.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *a: fncDir)
|
assert tstConf._confPath == fncPath
|
||||||
tstConf.initConfig()
|
assert tstConf._dataPath == fncPath
|
||||||
assert tstConf._confPath == os.path.join(fncDir, tstConf.appHandle)
|
assert confFile.exists()
|
||||||
assert tstConf._dataPath == os.path.join(fncDir, tstConf.appHandle)
|
|
||||||
assert not os.path.isfile(confFile)
|
|
||||||
|
|
||||||
# Fail to make folders
|
# Check that we have a default file
|
||||||
with monkeypatch.context() as mp:
|
copyfile(confFile, testFile)
|
||||||
mp.setattr("os.mkdir", causeOSError)
|
ignore = ("timestamp", "lastnotes", "guilang", "lastpath")
|
||||||
|
assert cmpFiles(testFile, compFile, ignoreStart=ignore)
|
||||||
|
tstConf.errorText() # This clears the error cache
|
||||||
|
|
||||||
tstConfDir = os.path.join(fncDir, "test_conf")
|
# Block saving the file
|
||||||
tstConf.initConfig(confPath=tstConfDir, dataPath=tmpDir)
|
|
||||||
assert tstConf._confPath is None
|
|
||||||
assert tstConf._dataPath == tmpDir
|
|
||||||
assert not os.path.isfile(confFile)
|
|
||||||
|
|
||||||
tstDataDir = os.path.join(fncDir, "test_data")
|
|
||||||
tstConf.initConfig(confPath=tmpDir, dataPath=tstDataDir)
|
|
||||||
assert tstConf._confPath == tmpDir
|
|
||||||
assert tstConf._dataPath is None
|
|
||||||
assert os.path.isfile(confFile)
|
|
||||||
os.unlink(confFile)
|
|
||||||
|
|
||||||
# Test load/save with no path
|
|
||||||
tstConf._confPath = None
|
|
||||||
assert tstConf.loadConfig() is False
|
|
||||||
assert tstConf.saveConfig() is False
|
|
||||||
|
|
||||||
# Run again and set the paths directly and correctly
|
|
||||||
# This should create a config file as well
|
|
||||||
with monkeypatch.context() as mp:
|
|
||||||
mp.setattr("os.path.expanduser", lambda *a: "")
|
|
||||||
tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
|
|
||||||
assert tstConf._confPath == tmpDir
|
|
||||||
assert tstConf._dataPath == tmpDir
|
|
||||||
assert os.path.isfile(confFile)
|
|
||||||
|
|
||||||
copyfile(confFile, testFile)
|
|
||||||
assert cmpFiles(testFile, compFile, ignoreStart=("timestamp", "lastnotes", "guilang"))
|
|
||||||
|
|
||||||
# Load and save with OSError
|
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
mp.setattr("builtins.open", causeOSError)
|
mp.setattr("builtins.open", causeOSError)
|
||||||
|
assert tstConf.saveConfig() is False
|
||||||
assert not tstConf.loadConfig()
|
|
||||||
assert tstConf.hasError is True
|
assert tstConf.hasError is True
|
||||||
assert tstConf.errData != []
|
assert tstConf.errorText().startswith("Could not save config file")
|
||||||
assert tstConf.getErrData().startswith("Could not")
|
|
||||||
assert tstConf.hasError is False
|
|
||||||
assert tstConf.errData == []
|
|
||||||
|
|
||||||
assert not tstConf.saveConfig()
|
# Block loading the file
|
||||||
assert tstConf.hasError is True
|
|
||||||
assert tstConf.errData != []
|
|
||||||
assert tstConf.getErrData().startswith("Could not")
|
|
||||||
assert tstConf.hasError is False
|
|
||||||
assert tstConf.errData == []
|
|
||||||
|
|
||||||
# Check handling of novelWriter as a package
|
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
|
mp.setattr("builtins.open", causeOSError)
|
||||||
assert tstConf._confPath == tmpDir
|
assert tstConf.loadConfig() is False
|
||||||
assert tstConf._dataPath == tmpDir
|
assert tstConf.hasError is True
|
||||||
appRoot = tstConf._appRoot
|
assert tstConf.errorText().startswith("Could not load config file")
|
||||||
|
|
||||||
mp.setattr("os.path.isfile", lambda *a: True)
|
# Change a few settings, save, reset, and reload
|
||||||
tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
|
tstConf.guiTheme = "foo"
|
||||||
assert tstConf._confPath == tmpDir
|
tstConf.guiSyntax = "bar"
|
||||||
assert tstConf._dataPath == tmpDir
|
|
||||||
assert tstConf._appRoot == os.path.dirname(appRoot)
|
|
||||||
assert tstConf._appPath == os.path.dirname(appRoot)
|
|
||||||
|
|
||||||
assert tstConf.loadConfig() is True
|
|
||||||
assert tstConf.saveConfig() is True
|
assert tstConf.saveConfig() is True
|
||||||
|
|
||||||
# Test Correcting Quote Settings
|
newConf = Config()
|
||||||
origDbl = tstConf.fmtDoubleQuotes
|
newConf.initConfig(confPath=fncPath, dataPath=fncPath)
|
||||||
origSng = tstConf.fmtSingleQuotes
|
assert newConf.guiTheme == "foo"
|
||||||
orDoDbl = tstConf.doReplaceDQuote
|
assert newConf.guiSyntax == "bar"
|
||||||
orDoSng = tstConf.doReplaceSQuote
|
|
||||||
|
|
||||||
|
# Test Correcting Quote Settings
|
||||||
tstConf.fmtDoubleQuotes = ["\"", "\""]
|
tstConf.fmtDoubleQuotes = ["\"", "\""]
|
||||||
tstConf.fmtSingleQuotes = ["'", "'"]
|
tstConf.fmtSingleQuotes = ["'", "'"]
|
||||||
tstConf.doReplaceDQuote = True
|
tstConf.doReplaceDQuote = True
|
||||||
tstConf.doReplaceSQuote = True
|
tstConf.doReplaceSQuote = True
|
||||||
assert tstConf.saveConfig() is True
|
assert tstConf.saveConfig() is True
|
||||||
|
|
||||||
assert tstConf.loadConfig() is True
|
assert newConf.loadConfig() is True
|
||||||
assert tstConf.doReplaceDQuote is False
|
assert newConf.doReplaceDQuote is False
|
||||||
assert tstConf.doReplaceSQuote is False
|
assert newConf.doReplaceSQuote is False
|
||||||
|
|
||||||
tstConf.fmtDoubleQuotes = origDbl
|
# END Test testBaseConfig_InitLoadSave
|
||||||
tstConf.fmtSingleQuotes = origSng
|
|
||||||
tstConf.doReplaceDQuote = orDoDbl
|
|
||||||
tstConf.doReplaceSQuote = orDoSng
|
@pytest.mark.base
|
||||||
assert tstConf.saveConfig() is True
|
def testBaseConfig_Localisation(fncPath, tstPaths):
|
||||||
|
"""Test localisation.
|
||||||
|
"""
|
||||||
|
tstConf = Config()
|
||||||
|
tstConf.initConfig(confPath=fncPath, dataPath=fncPath)
|
||||||
|
|
||||||
# Localisation
|
# Localisation
|
||||||
# ============
|
# ============
|
||||||
|
|
||||||
i18nDir = os.path.join(fncDir, "i18n")
|
i18nDir = fncPath / "i18n"
|
||||||
os.mkdir(i18nDir)
|
i18nDir.mkdir()
|
||||||
os.mkdir(os.path.join(i18nDir, "stuff"))
|
|
||||||
tstConf._nwLangPath = i18nDir
|
tstConf._nwLangPath = i18nDir
|
||||||
|
|
||||||
copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(i18nDir, "nw_en_GB.qm"))
|
copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_en_GB.qm")
|
||||||
writeFile(os.path.join(i18nDir, "nw_en_GB.ts"), "")
|
writeFile(i18nDir / "nw_en_GB.ts", "")
|
||||||
writeFile(os.path.join(i18nDir, "nw_abcd.qm"), "")
|
writeFile(i18nDir / "nw_abcd.qm", "")
|
||||||
|
|
||||||
tstApp = MockApp()
|
tstApp = MockApp()
|
||||||
tstConf.initLocalisation(tstApp)
|
tstConf.initLocalisation(tstApp)
|
||||||
@@ -217,82 +184,55 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
|
|||||||
assert theList == []
|
assert theList == []
|
||||||
|
|
||||||
# Add Language
|
# Add Language
|
||||||
copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(i18nDir, "nw_fr.qm"))
|
copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_fr.qm")
|
||||||
writeFile(os.path.join(i18nDir, "nw_fr.ts"), "")
|
writeFile(i18nDir / "nw_fr.ts", "")
|
||||||
|
|
||||||
theList = tstConf.listLanguages(tstConf.LANG_NW)
|
theList = tstConf.listLanguages(tstConf.LANG_NW)
|
||||||
assert theList == [("en_GB", "British English"), ("fr", "Français")]
|
assert theList == [("en_GB", "British English"), ("fr", "Français")]
|
||||||
|
|
||||||
copyfile(confFile, testFile)
|
# END Test testBaseConfig_Localisation
|
||||||
assert cmpFiles(testFile, compFile, ignoreStart=("timestamp", "lastnotes", "guilang"))
|
|
||||||
|
|
||||||
# END Test testBaseConfig_Init
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.base
|
@pytest.mark.base
|
||||||
def testBaseConfig_RecentCache(monkeypatch, tmpConf, tmpDir, fncDir):
|
def testBaseConfig_Methods(tmpConf, tmpPath):
|
||||||
"""Test recent cache file.
|
"""Check class methods.
|
||||||
"""
|
"""
|
||||||
# Add a couple of values
|
# Data Path
|
||||||
pathOne = os.path.join(fncDir, "projPathOne", nwFiles.PROJ_FILE)
|
assert tmpConf.dataPath() == tmpPath
|
||||||
pathTwo = os.path.join(fncDir, "projPathTwo", nwFiles.PROJ_FILE)
|
assert tmpConf.dataPath("stuff") == tmpPath / "stuff"
|
||||||
assert tmpConf.updateRecentCache(pathOne, "Proj One", 100, 1600002000)
|
|
||||||
assert tmpConf.updateRecentCache(pathTwo, "Proj Two", 200, 1600005600)
|
|
||||||
assert tmpConf.recentProj == {
|
|
||||||
pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
|
|
||||||
pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Fail to Save
|
# Assets Path
|
||||||
with monkeypatch.context() as mp:
|
appPath = tmpConf._appPath
|
||||||
mp.setattr("builtins.open", causeOSError)
|
assert tmpConf.assetPath() == appPath / "assets"
|
||||||
assert not tmpConf.saveRecentCache()
|
assert tmpConf.assetPath("stuff") == appPath / "assets" / "stuff"
|
||||||
|
|
||||||
# Save Proper
|
# Last Path
|
||||||
cacheFile = os.path.join(tmpDir, nwFiles.RECENT_FILE)
|
assert tmpConf.lastPath() == tmpPath
|
||||||
assert tmpConf.saveRecentCache()
|
|
||||||
assert tmpConf.saveRecentCache()
|
|
||||||
assert os.path.isfile(cacheFile)
|
|
||||||
|
|
||||||
# Fail to Load
|
tmpStuff = tmpPath / "stuff"
|
||||||
with monkeypatch.context() as mp:
|
tmpStuff.mkdir()
|
||||||
mp.setattr("builtins.open", causeOSError)
|
tmpConf.setLastPath(tmpStuff)
|
||||||
tmpConf.recentProj = {}
|
assert tmpConf.lastPath() == tmpStuff
|
||||||
assert not tmpConf.loadRecentCache()
|
|
||||||
assert tmpConf.recentProj == {}
|
|
||||||
|
|
||||||
# Load Proper
|
fileStuff = tmpStuff / "more_stuff.txt"
|
||||||
tmpConf.recentProj = {}
|
fileStuff.write_text("Stuff")
|
||||||
assert tmpConf.loadRecentCache()
|
tmpConf.setLastPath(fileStuff)
|
||||||
assert tmpConf.recentProj == {
|
assert tmpConf.lastPath() == tmpStuff
|
||||||
pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
|
|
||||||
pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Remove Non-Existent Entry
|
fileStuff.unlink()
|
||||||
assert not tmpConf.removeFromRecentCache("stuff")
|
tmpStuff.rmdir()
|
||||||
assert tmpConf.recentProj == {
|
assert tmpConf.lastPath() == Path.home().absolute()
|
||||||
pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
|
|
||||||
pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Remove Second Entry
|
# Recent Projects
|
||||||
assert tmpConf.removeFromRecentCache(pathTwo)
|
assert isinstance(tmpConf.recentProjects, RecentProjects)
|
||||||
assert tmpConf.recentProj == {
|
|
||||||
pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
|
|
||||||
}
|
|
||||||
|
|
||||||
# END Test testBaseConfig_RecentCache
|
# END Test testBaseConfig_Methods
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.base
|
@pytest.mark.base
|
||||||
def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
|
def testBaseConfig_SettersGetters(tmpConf):
|
||||||
"""Set various sizes and positions
|
"""Set various sizes and positions
|
||||||
"""
|
"""
|
||||||
confFile = os.path.join(tmpDir, "novelwriter.conf")
|
|
||||||
testFile = os.path.join(outDir, "baseConfig_novelwriter.conf")
|
|
||||||
compFile = os.path.join(refDir, "baseConfig_novelwriter.conf")
|
|
||||||
|
|
||||||
# GUI Scaling
|
# GUI Scaling
|
||||||
# ===========
|
# ===========
|
||||||
|
|
||||||
@@ -439,17 +379,6 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
|
|||||||
tmpConf.setViewSynopsis(True)
|
tmpConf.setViewSynopsis(True)
|
||||||
assert tmpConf.viewSynopsis is True
|
assert tmpConf.viewSynopsis is True
|
||||||
|
|
||||||
# Check Final File
|
|
||||||
# ================
|
|
||||||
|
|
||||||
assert tmpConf.confChanged is True
|
|
||||||
assert tmpConf.saveConfig() is True
|
|
||||||
assert tmpConf.confChanged is False
|
|
||||||
|
|
||||||
copyfile(confFile, testFile)
|
|
||||||
ignore = ("timestamp", "lastnotes", "guilang", "lastpath")
|
|
||||||
assert cmpFiles(testFile, compFile, ignoreStart=ignore)
|
|
||||||
|
|
||||||
# END Test testBaseConfig_SettersGetters
|
# END Test testBaseConfig_SettersGetters
|
||||||
|
|
||||||
|
|
||||||
@@ -479,3 +408,68 @@ def testBaseConfig_Internal(monkeypatch, tmpConf):
|
|||||||
assert tmpConf.hasEnchant is False
|
assert tmpConf.hasEnchant is False
|
||||||
|
|
||||||
# END Test testBaseConfig_Internal
|
# END Test testBaseConfig_Internal
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.base
|
||||||
|
def testBaseConfig_RecentCache(monkeypatch, fncPath):
|
||||||
|
"""Test recent cache file.
|
||||||
|
"""
|
||||||
|
cacheFile = fncPath / nwFiles.RECENT_FILE
|
||||||
|
recent = RecentProjects(fncPath)
|
||||||
|
|
||||||
|
# Load when there is no file should pass, but load nothing
|
||||||
|
assert not cacheFile.exists()
|
||||||
|
assert recent.loadCache() is True
|
||||||
|
assert recent.listEntries() == []
|
||||||
|
|
||||||
|
# Add a couple of values
|
||||||
|
pathOne = fncPath / "projPathOne" / nwFiles.PROJ_FILE
|
||||||
|
pathTwo = fncPath / "projPathTwo" / nwFiles.PROJ_FILE
|
||||||
|
|
||||||
|
recent.update(pathOne, "Proj One", 100, 1600002000)
|
||||||
|
recent.update(pathTwo, "Proj Two", 200, 1600005600)
|
||||||
|
assert recent.listEntries() == [
|
||||||
|
(str(pathOne), "Proj One", 100, 1600002000),
|
||||||
|
(str(pathTwo), "Proj Two", 200, 1600005600),
|
||||||
|
]
|
||||||
|
assert cacheFile.exists()
|
||||||
|
cacheFile.unlink()
|
||||||
|
assert not cacheFile.exists()
|
||||||
|
|
||||||
|
# Fail to Save
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("builtins.open", causeOSError)
|
||||||
|
assert recent.saveCache() is False
|
||||||
|
assert not cacheFile.exists()
|
||||||
|
|
||||||
|
# Save Proper
|
||||||
|
assert recent.saveCache() is True
|
||||||
|
assert cacheFile.exists()
|
||||||
|
|
||||||
|
# Fail to Load
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("builtins.open", causeOSError)
|
||||||
|
assert recent.loadCache() is False
|
||||||
|
assert recent.listEntries() == []
|
||||||
|
|
||||||
|
# Load Proper
|
||||||
|
assert recent.loadCache() is True
|
||||||
|
assert recent.listEntries() == [
|
||||||
|
(str(pathOne), "Proj One", 100, 1600002000),
|
||||||
|
(str(pathTwo), "Proj Two", 200, 1600005600),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Remove Non-Existent Entry
|
||||||
|
recent.remove("stuff")
|
||||||
|
assert recent.listEntries() == [
|
||||||
|
(str(pathOne), "Proj One", 100, 1600002000),
|
||||||
|
(str(pathTwo), "Proj Two", 200, 1600005600),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Remove Second Entry
|
||||||
|
recent.remove(pathTwo)
|
||||||
|
assert recent.listEntries() == [
|
||||||
|
(str(pathOne), "Proj One", 100, 1600002000),
|
||||||
|
]
|
||||||
|
|
||||||
|
# END Test testBaseConfig_RecentCache
|
||||||
|
|||||||
Reference in New Issue
Block a user