Merge pull request #165 from vkbo/file_save_update

File saving and opening
This commit is contained in:
Veronica K. Berglyd Olsen
2020-02-13 21:27:26 +01:00
committed by GitHub
7 changed files with 114 additions and 81 deletions
-1
View File
@@ -22,7 +22,6 @@ class nwFiles():
APP_ICON = "novelWriter.svg"
PROJ_FILE = "nwProject.nwx"
PROJ_COUNT = "projCount.txt"
PROJ_DICT = "wordlist.txt"
SESS_INFO = "sessionInfo.log"
INDEX_FILE = "tagsIndex.json"
+3 -3
View File
@@ -330,7 +330,7 @@ class GuiMain(QMainWindow):
return True
def saveProject(self, isAuto=False):
def saveProject(self):
"""Save the current project.
"""
if not self.hasProject:
@@ -344,7 +344,7 @@ class GuiMain(QMainWindow):
return False
self.treeView.saveTreeOrder()
self.theProject.saveProject(isAuto)
self.theProject.saveProject()
self.theIndex.saveIndex()
self.mainMenu.updateRecentProjects()
@@ -840,7 +840,7 @@ class GuiMain(QMainWindow):
if (self.hasProject and self.theProject.projChanged and
self.theProject.projPath is not None):
logger.debug("Autosaving project")
self.saveProject(isAuto=True)
self.saveProject()
return
def _autoSaveDocument(self):
+13 -15
View File
@@ -100,25 +100,23 @@ class NWDoc():
mkdir(dataPath)
logger.debug("Created folder %s" % dataPath)
docTemp = path.join(dataPath,docFile[:-3]+"tmp")
docBack = path.join(dataPath,docFile[:-3]+"bak")
if path.isfile(docTemp):
unlink(docTemp)
if path.isfile(docBack):
rename(docBack,docTemp)
if path.isfile(docPath):
rename(docPath,docBack)
docTemp = path.join(dataPath, docFile+"~")
docBack = path.join(dataPath, docFile[:-3]+"bak")
try:
with open(docPath,mode="w",encoding="utf8") as outFile:
with open(docTemp,mode="w",encoding="utf8") as outFile:
outFile.write(docText)
except Exception as e:
self.makeAlert(["Could not save document.",str(e)], nwAlert.ERROR)
return False
if path.isfile(docTemp):
unlink(docTemp)
# If we're here, the file was successfully saved,
# so let's sort out the temps and backups
if path.isfile(docBack):
unlink(docBack)
if path.isfile(docPath):
rename(docPath, docBack)
rename(docTemp, docPath)
self.theParent.statusBar.setStatus("Saved Document: %s" % self.theItem.itemName)
@@ -132,8 +130,8 @@ class NWDoc():
dataPath = path.join(self.theProject.projPath, docDir)
chkList = []
chkList.append(path.join(dataPath, docFile))
chkList.append(path.join(dataPath,docFile[:-3]+"tmp"))
chkList.append(path.join(dataPath,docFile[:-3]+"bak"))
chkList.append(path.join(dataPath, docFile+"~"))
chkList.append(path.join(dataPath, docFile[:-3]+"bak"))
for chkFile in chkList:
if path.isfile(chkFile):
try:
@@ -147,7 +145,7 @@ class NWDoc():
@staticmethod
def assemblePath(tHandle, docExt):
if tHandle is None:
return None
return None, None
docDir = "data_"+tHandle[0]
docFile = tHandle[1:13]+"_"+docExt
return docDir, docFile
+49 -60
View File
@@ -13,7 +13,7 @@
import logging
import nw
from os import path, mkdir, listdir, unlink
from os import path, mkdir, listdir, unlink, rename
from shutil import copyfile
from lxml import etree
from hashlib import sha256
@@ -22,6 +22,7 @@ from time import time
from nw.project.status import NWStatus
from nw.project.item import NWItem
from nw.tools import projectMaintenance
from nw.common import checkString, checkBool, checkInt
from nw.constants import (
nwFiles, nwConst, nwItemType, nwItemClass, nwItemLayout, nwAlert
@@ -50,7 +51,6 @@ class NWProject():
self.trashRoot = None # The handle of the trash root folder
self.projPath = None # The full path to where the currently open project is saved
self.projMeta = None # The full path to the project's meta data folder
self.projCache = None # The full path to the project's cache folder
self.projDict = None # The spell check dictionary
self.projFile = None # The file name of the project main xml file
@@ -154,7 +154,6 @@ class NWProject():
self.trashRoot = None
self.projPath = None
self.projMeta = None
self.projCache = None
self.projDict = None
self.projFile = nwFiles.PROJ_FILE
self.projName = ""
@@ -180,6 +179,11 @@ class NWProject():
return
def openProject(self, fileName):
"""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
build the tree of project items.
"""
if not path.isfile(fileName):
fileName = path.join(fileName, nwFiles.PROJ_FILE)
@@ -191,21 +195,35 @@ class NWProject():
self.projPath = path.dirname(fileName)
logger.debug("Opening project: %s" % self.projPath)
self.projMeta = path.join(self.projPath,"meta")
self.projCache = path.join(self.projPath,"cache")
self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT)
self.projMeta = path.join(self.projPath,"meta")
self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT)
if not self._checkFolder(self.projMeta):
return
if not self._checkFolder(self.projCache):
return
try:
projectMaintenance(self)
except Exception as E:
logger.error(str(E))
try:
nwXML = etree.parse(fileName)
except Exception as e:
self.makeAlert(["Failed to parse project xml.",str(e)], nwAlert.ERROR)
self.clearProject()
return False
# Trying to open backup file instead
backFile = fileName[:-3]+"bak"
if path.isfile(backFile):
self.makeAlert("Attempting to open backup project file instead.", nwAlert.INFO)
try:
nwXML = etree.parse(backFile)
except Exception as e:
self.makeAlert(["Failed to parse project xml.",str(e)], nwAlert.ERROR)
self.clearProject()
return False
else:
self.clearProject()
return False
xRoot = nwXML.getroot()
nwxRoot = xRoot.tag
@@ -288,25 +306,24 @@ class NWProject():
return True
def saveProject(self, isAuto=False):
def saveProject(self):
"""Save the project main XML file. The saving command itself
uses a temporary filename, and the file is renamed afterwards to
make sure if the save fails, we're not left with a truncated
file.
"""
if self.projPath is None:
self.makeAlert("Project path not set, cannot save.", nwAlert.ERROR)
return False
self.projMeta = path.join(self.projPath,"meta")
self.projCache = path.join(self.projPath,"cache")
self.projMeta = path.join(self.projPath,"meta")
if not self._checkFolder(self.projPath): return
if not self._checkFolder(self.projMeta): return
if not self._checkFolder(self.projCache): return
if not self._checkFolder(self.projPath): return
if not self._checkFolder(self.projMeta): return
logger.debug("Saving project: %s" % self.projPath)
# Save a copy of the current file, just in case
if not isAuto:
self._maintainPrevious()
# Root element and project details
logger.debug("Writing project meta")
nwXML = etree.Element("novelWriterXML",attrib={
@@ -345,9 +362,11 @@ class NWProject():
self.projTree[tHandle].packXML(xContent)
# Write the xml tree to file
saveFile = path.join(self.projPath,self.projFile)
tempFile = path.join(self.projPath, self.projFile+"~")
saveFile = path.join(self.projPath, self.projFile)
backFile = path.join(self.projPath, self.projFile[:-3]+"bak")
try:
with open(saveFile,mode="wb") as outFile:
with open(tempFile, mode="wb") as outFile:
outFile.write(etree.tostring(
nwXML,
pretty_print = True,
@@ -358,6 +377,14 @@ class NWProject():
self.makeAlert(["Failed to save project.",str(e)], nwAlert.ERROR)
return False
# If we're here, the file was successfully saved,
# so let's sort out the temps and backups
if path.isfile(backFile):
unlink(backFile)
if path.isfile(saveFile):
rename(saveFile, backFile)
rename(tempFile, saveFile)
self.mainConf.setRecent(self.projPath)
self.theParent.setStatus("Saved Project: %s" % self.projName)
self.setProjectChanged(False)
@@ -740,42 +767,4 @@ class NWProject():
itemHandle = self._makeHandle(addSeed+"!")
return itemHandle
def _maintainPrevious(self):
"""This function will take the current project file and copy it
into the project cache folder with an incremental file extension
added. These serve as a backup in case the xml file gets
corrupted.
"""
countFile = path.join(self.projCache, nwFiles.PROJ_COUNT)
projCount = 0
if path.isfile(countFile):
try:
with open(countFile, mode="r") as inFile:
projCount = int(inFile.read())+1
except:
projCount = 0
if projCount > 9:
projCount = 0
projBackup = "%s.%d" % (nwFiles.PROJ_FILE, projCount)
try:
copyfile(
path.join(self.projPath, self.projFile),
path.join(self.projCache, projBackup)
)
except:
logger.error("Failed to write to file %s" % projBackup)
try:
with open(countFile, mode="w") as outFile:
outFile.write(str(projCount))
except:
logger.error("Failed to write to file %s" % countFile)
return
# END Class NWProject
+2
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from nw.tools.analyse import TextAnalysis
from nw.tools.legacy import projectMaintenance
from nw.tools.optlaststate import OptLastState
from nw.tools.spellcheck import NWSpellCheck
from nw.tools.spellenchant import NWSpellEnchant
@@ -10,6 +11,7 @@ from nw.tools.wordcount import countWords
__all__ = [
"TextAnalysis",
"projectMaintenance",
"OptLastState",
"NWSpellCheck",
"NWSpellEnchant",
+47
View File
@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
"""novelWriter Legacy Tools
novelWriter Legacy Tools
============================
Various functions to handle old projects
File History:
Created: 2020-02-13 [0.4.3]
"""
import logging
import nw
from os import path, unlink, rmdir
logger = logging.getLogger(__name__)
def projectMaintenance(theProject):
"""Wrapper class for handling various tasks related to managing old
projects with content from older versions of novelWriter.
"""
# Remove no longer used project cache folder
if path.isdir(theProject.projPath):
cacheDir = path.join(theProject.projPath, "cache")
if path.isdir(cacheDir):
logger.info("Deprecated cache folder found")
rmList = []
for i in range(10):
rmList.append(path.join(cacheDir, "nwProject.nwx.%d" % i))
rmList.append(path.join(cacheDir, "projCount.txt"))
for rmFile in rmList:
if path.isfile(rmFile):
logger.info("Deleting: %s" % rmFile)
try:
unlink(rmFile)
except Exception as e:
logger.error(str(e))
logger.info("Deleting: %s" % cacheDir)
try:
rmdir(cacheDir)
except Exception as e:
logger.error(str(e))
return
-2
View File
@@ -37,7 +37,6 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
assert nwGUI.theProject.trashRoot is None
assert nwGUI.theProject.projPath is None
assert nwGUI.theProject.projMeta is None
assert nwGUI.theProject.projCache is None
assert nwGUI.theProject.projFile == "nwProject.nwx"
assert nwGUI.theProject.projName == ""
assert nwGUI.theProject.bookTitle == ""
@@ -62,7 +61,6 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
assert nwGUI.theProject.trashRoot is None
assert nwGUI.theProject.projPath == nwTempGUI
assert nwGUI.theProject.projMeta == path.join(nwTempGUI,"meta")
assert nwGUI.theProject.projCache == path.join(nwTempGUI,"cache")
assert nwGUI.theProject.projFile == "nwProject.nwx"
assert nwGUI.theProject.projName == ""
assert nwGUI.theProject.bookTitle == ""