Fix missing data path issue when creating themes folders (#1184)

This commit is contained in:
Veronica Berglyd Olsen
2022-10-18 21:01:23 +02:00
committed by GitHub
5 changed files with 90 additions and 123 deletions
+20 -1
View File
@@ -35,7 +35,7 @@ from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWidgets import qApp
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
from novelwriter.error import logException
from novelwriter.error import formatException, logException
from novelwriter.constants import nwConst, nwUnicode
logger = logging.getLogger(__name__)
@@ -475,6 +475,25 @@ def makeFileNameSafe(value):
return cleanName
def ensureFolder(dirPath, parentPath=None, errLog=None):
"""Make sure a folder exists, and if it doesn't, create it.
"""
try:
if parentPath:
dirPath = os.path.join(parentPath, dirPath)
if not os.path.isdir(dirPath):
os.mkdir(dirPath)
except Exception as exc:
logger.error("Could not create folder: %s", dirPath)
logException()
if isinstance(errLog, list):
errLog.append(f"Could not create folder: {dirPath}")
errLog.append(formatException(exc))
return False
return True
def sha256sum(filePath):
"""Make a shasum of a file using a buffer.
Based on: https://stackoverflow.com/a/44873382/5825851
+38 -87
View File
@@ -37,7 +37,7 @@ from PyQt5.QtCore import (
)
from novelwriter.error import logException, formatException
from novelwriter.common import splitVersionNumber, formatTimeStamp, NWConfigParser
from novelwriter.common import ensureFolder, splitVersionNumber, formatTimeStamp, NWConfigParser
from novelwriter.constants import nwFiles, nwUnicode
logger = logging.getLogger(__name__)
@@ -54,23 +54,21 @@ class Config:
self.appName = "novelWriter"
self.appHandle = self.appName.lower()
# Config Error Handling
self.hasError = False # True if the config class encountered an error
self.errData = [] # List of error messages
# Set Paths
self.cmdOpen = None # Path from command line for project to be opened on launch
self.confPath = None # Folder where the config is saved
self.confFile = None # The config file name
self.dataPath = None # Folder where app data is stored
self.lastPath = None # The last user-selected folder (browse dialogs)
self.appPath = None # The full path to the novelwriter package folder
self.appRoot = None # The full path to the novelwriter root folder
self.appIcon = None # The full path to the novelwriter icon file
self.assetPath = None # The full path to the novelwriter/assets folder
self.pdfDocs = None # The location of the PDF manual, if it exists
self.cmdOpen = None # Path from command line for project to be opened on launch
self.confPath = None # Folder where the config is saved
self.confFile = None # The config file name
self.dataPath = None # Folder where app data is stored
self.lastPath = None # The last user-selected folder (browse dialogs)
self.appPath = None # The full path to the novelwriter package folder
self.appRoot = None # The full path to the novelwriter root folder
self.appIcon = None # The full path to the novelwriter icon file
self.assetPath = None # The full path to the novelwriter/assets folder
self.pdfDocs = None # The location of the PDF manual, if it exists
# Runtime Settings and Variables
self.hasError = False # True if the config class encountered an error
self.errData = [] # List of error messages
self.confChanged = False # True whenever the config has chenged, false after save
# General
@@ -94,14 +92,14 @@ class Config:
self.qtTrans = {}
# Sizes
self.winGeometry = [1200, 650]
self.prefGeometry = [700, 615]
self.projColWidth = [200, 60, 140]
self.mainPanePos = [300, 800]
self.docPanePos = [400, 400]
self.viewPanePos = [500, 150]
self.outlnPanePos = [500, 150]
self.isFullScreen = False
self.winGeometry = [1200, 650]
self.prefGeometry = [700, 615]
self.projColWidth = [200, 60, 140]
self.mainPanePos = [300, 800]
self.docPanePos = [400, 400]
self.viewPanePos = [500, 150]
self.outlnPanePos = [500, 150]
self.isFullScreen = False
# Features
self.hideVScroll = False # Hide vertical scroll bars on main widgets
@@ -270,20 +268,8 @@ class Config:
logger.info("Setting data path from alternative path: %s", dataPath)
self.dataPath = dataPath
logger.verbose("Config path: %s", self.confPath)
logger.verbose("Data path: %s", self.dataPath)
# Check Data Path Subdirs
dataDirs = ["syntax", "themes"]
for dataDir in dataDirs:
dirPath = os.path.join(self.dataPath, dataDir)
if not os.path.isdir(dirPath):
try:
os.mkdir(dirPath)
logger.info("Created folder: %s", dirPath)
except Exception:
logger.error("Could not create folder: %s", dirPath)
logException()
logger.debug("Config path: %s", self.confPath)
logger.debug("Data path: %s", self.dataPath)
self.confFile = self.appHandle+".conf"
self.lastPath = os.path.expanduser("~")
@@ -308,18 +294,20 @@ class Config:
logger.verbose("App path: %s", self.appPath)
logger.verbose("Last path: %s", self.lastPath)
# If the config folder does not exist, create it.
# This assumes that the os config folder itself exists.
if not os.path.isdir(self.confPath):
try:
os.mkdir(self.confPath)
except Exception as exc:
logger.error("Could not create folder: %s", self.confPath)
logException()
self.hasError = True
self.errData.append("Could not create folder: %s" % self.confPath)
self.errData.append(formatException(exc))
self.confPath = None
# If the config and data folders don't not exist, create them
# This assumes that the os config and data folders exist
if not ensureFolder(self.confPath, errLog=self.errData):
self.hasError = True
self.confPath = None
if not ensureFolder(self.dataPath, errLog=self.errData):
self.hasError = True
self.dataPath = None
# We don't error on these failing since they are not essential
if self.dataPath is not None:
ensureFolder("syntax", parentPath=self.dataPath)
ensureFolder("themes", parentPath=self.dataPath)
# Check if config file exists
if self.confPath is not None:
@@ -330,20 +318,6 @@ class Config:
# If it does not exist, save a copy of the default values
self.saveConfig()
# If the data folder does not exist, create it.
# This assumes that the os data folder itself exists.
if self.dataPath is not None:
if not os.path.isdir(self.dataPath):
try:
os.mkdir(self.dataPath)
except Exception as exc:
logger.error("Could not create folder: %s", self.dataPath)
logException()
self.hasError = True
self.errData.append("Could not create folder: %s" % self.dataPath)
self.errData.append(formatException(exc))
self.dataPath = None
# Load recent projects cache
self.loadRecentCache()
@@ -389,7 +363,7 @@ class Config:
def listLanguages(self, lngSet):
"""List localisation files in the i18n folder. The default GUI
language 'en_GB' is British English.
language is British English (en_GB).
"""
if lngSet == self.LANG_NW:
fPre = "nw_"
@@ -741,29 +715,6 @@ class Config:
# Setters
##
def setConfPath(self, newPath):
"""Set the path and filename to the config file.
"""
if newPath is None:
return True
if not os.path.isfile(newPath):
logger.error("File not found, using default config path instead")
return False
self.confPath = os.path.dirname(newPath)
self.confFile = os.path.basename(newPath)
return True
def setDataPath(self, newPath):
"""Set the data path.
"""
if newPath is None:
return True
if not os.path.isdir(newPath):
logger.error("Path not found, using default data path instead")
return False
self.dataPath = os.path.abspath(newPath)
return True
def setLastPath(self, lastPath):
"""Set the last used path (by the user).
"""
+4 -2
View File
@@ -120,11 +120,13 @@ class GuiTheme:
self._availThemes = {}
self._availSyntax = {}
self._listConf(self._availSyntax, os.path.join(self.mainConf.dataPath, "syntax"))
self._listConf(self._availSyntax, os.path.join(self.mainConf.assetPath, "syntax"))
self._listConf(self._availThemes, os.path.join(self.mainConf.dataPath, "themes"))
self._listConf(self._availThemes, os.path.join(self.mainConf.assetPath, "themes"))
if self.mainConf.dataPath: # Not guaranteed to be set
self._listConf(self._availSyntax, os.path.join(self.mainConf.dataPath, "syntax"))
self._listConf(self._availThemes, os.path.join(self.mainConf.dataPath, "themes"))
self.updateFont()
self.updateTheme()
self.iconCache.updateTheme()
+28 -1
View File
@@ -33,7 +33,8 @@ from novelwriter.common import (
isTitleTag, isItemClass, isItemType, isItemLayout, hexToInt, checkIntRange,
minmax, checkIntTuple, formatInt, formatTimeStamp, formatTime, simplified,
splitVersionNumber, transferCase, fuzzyTime, numberToRoman, jsonEncode,
readTextFile, makeFileNameSafe, sha256sum, getGuiItem, NWConfigParser
readTextFile, makeFileNameSafe, ensureFolder, sha256sum, getGuiItem,
NWConfigParser
)
@@ -541,6 +542,32 @@ def testBaseCommon_MakeFileNameSafe():
# END Test testBaseCommon_MakeFileNameSafe
@pytest.mark.base
def testBaseCommon_EnsureFolder(monkeypatch, fncDir):
"""Test the ensureFolder function.
"""
newDir1 = os.path.join(fncDir, "newDir1")
newDir2 = os.path.join(fncDir, "newDir2")
newDir3 = os.path.join(fncDir, "newDir3")
assert ensureFolder(None) is False
assert ensureFolder(newDir1) is True
assert os.path.isdir(newDir1)
assert ensureFolder("newDir2", parentPath=fncDir) is True
assert os.path.isdir(newDir2)
with monkeypatch.context() as mp:
mp.setattr("os.mkdir", causeOSError)
errLog = []
assert ensureFolder("newDir3", parentPath=fncDir, errLog=errLog) is False
assert errLog[0] == f"Could not create folder: {newDir3}"
assert not os.path.isdir(newDir3)
# END Test testBaseCommon_EnsureFolder
@pytest.mark.base
def testBaseCommon_Sha256Sum(monkeypatch, fncDir, ipsumText):
"""Test the sha256sum function.
-32
View File
@@ -316,38 +316,6 @@ def testBaseConfig_RecentCache(monkeypatch, tmpConf, tmpDir, fncDir):
# END Test testBaseConfig_RecentCache
@pytest.mark.base
def testBaseConfig_SetPath(tmpConf, tmpDir):
"""Test path setters.
"""
# Conf Path
assert tmpConf.setConfPath(None)
assert not tmpConf.setConfPath(os.path.join("somewhere", "over", "the", "rainbow"))
assert tmpConf.setConfPath(os.path.join(tmpDir, "novelwriter.conf"))
assert tmpConf.confPath == tmpDir
assert tmpConf.confFile == "novelwriter.conf"
assert not tmpConf.confChanged
# Data Path
assert tmpConf.setDataPath(None)
assert not tmpConf.setDataPath(os.path.join("somewhere", "over", "the", "rainbow"))
assert tmpConf.setDataPath(tmpDir)
assert tmpConf.dataPath == tmpDir
assert not tmpConf.confChanged
# Last Path
assert tmpConf.setLastPath(None)
assert tmpConf.lastPath == ""
assert tmpConf.setLastPath(os.path.join(tmpDir, "file.tmp"))
assert tmpConf.lastPath == tmpDir
assert tmpConf.setLastPath("")
assert tmpConf.lastPath == ""
# END Test testBaseConfig_SetPath
@pytest.mark.base
def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
"""Set various sizes and positions