Merge pull request #165 from vkbo/file_save_update
File saving and opening
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -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 == ""
|
||||
|
||||
Reference in New Issue
Block a user