diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index f1021fc3..a37ddf0c 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -177,6 +177,10 @@ class GuiMainMenu(QMenuBar): self.theParent.docEditor.revealLocation() return True + def _doBackup(self): + self.theProject.zipIt(True) + return True + ## # Menu Builders ## @@ -703,7 +707,7 @@ class GuiMainMenu(QMenuBar): # Tools > Backup self.aBackupProject = QAction("Backup Project", self) self.aBackupProject.setStatusTip("Backup Project") - self.aBackupProject.triggered.connect(self.theParent.backupProject) + self.aBackupProject.triggered.connect(self._doBackup) self.toolsMenu.addAction(self.aBackupProject) # Tools > Settings diff --git a/nw/guimain.py b/nw/guimain.py index 14e0816b..935c1fb2 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -45,7 +45,7 @@ from nw.gui import ( GuiConfigEditor, GuiProjectEditor, GuiItemEditor, GuiProjectOutline, GuiSessionLogView, GuiDocMerge, GuiDocSplit, GuiProjectLoad ) -from nw.project import NWProject, NWDoc, NWIndex, NWBackup +from nw.project import NWProject, NWDoc, NWIndex from nw.tools import countWords from nw.constants import nwFiles, nwItemType, nwAlert @@ -333,7 +333,7 @@ class GuiMain(QMainWindow): if msgRes != QMessageBox.Yes: doBackup = False if doBackup: - self.backupProject() + self.theProject.zipIt(False) else: saveOK = True @@ -450,13 +450,6 @@ class GuiMain(QMainWindow): return True - def backupProject(self): - """Trigger the project backup process. - """ - theBackup = NWBackup(self, self.theProject) - theBackup.zipIt() - return True - ## # Document Actions ## diff --git a/nw/project/__init__.py b/nw/project/__init__.py index b36f5a5e..f2a0fe0c 100644 --- a/nw/project/__init__.py +++ b/nw/project/__init__.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -from nw.project.backup import NWBackup from nw.project.document import NWDoc from nw.project.index import NWIndex from nw.project.project import NWProject __all__ = [ - "NWBackup", "NWDoc", "NWIndex", "NWProject", diff --git a/nw/project/backup.py b/nw/project/backup.py deleted file mode 100644 index 3b6b61f4..00000000 --- a/nw/project/backup.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter Project Backup - - novelWriter – Project Backup -============================== - Class handling project backups - - File History: - Created: 2019-06-16 [0.1.5] - - This file is a part of novelWriter - Copyright 2020, Veronica Berglyd Olsen - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import logging -import nw - -from os import path -from shutil import make_archive -from datetime import datetime - -from nw.constants import nwAlert - -logger = logging.getLogger(__name__) - -class NWBackup(): - - def __init__(self, theParent, theProject): - self.mainConf = nw.CONFIG - self.theParent = theParent - self.theProject = theProject - return - - def zipIt(self): - - if self.mainConf.backupPath is None or self.mainConf.backupPath == "": - self.theParent.makeAlert(( - "Cannot backup project because no backup path is set. " - "Please set a valid backup location in Tools > Preferences." - ), nwAlert.WARN) - return False - - if self.theProject.projName is None or self.theProject.projName == "": - self.theParent.makeAlert(( - "Cannot backup project because no project name is set. " - "Please set a Working Title in Project > Project Settings." - ), nwAlert.WARN) - return False - - if not path.isdir(self.mainConf.backupPath): - self.theParent.makeAlert(( - "Cannot backup project because the backup path does not exist. " - "Please set a valid backup location in Tools > Preferences." - ), nwAlert.WARN) - return False - - logger.info("Backing up project") - self.theParent.statusBar.setStatus("Backing up project ...") - - archName = "" - for c in self.theProject.projName: - if c.isalnum(): - archName += c - else: - archName += "_" - - archName = archName+"_"+datetime.now().strftime("%Y%m%d-%H%M%S") - baseName = path.join(self.mainConf.backupPath, archName) - - try: - self.theProject._clearLockFile() - make_archive(baseName, "zip", self.theProject.projPath, ".") - self.theProject._writeLockFile() - logger.info("Backup written to: %s" % archName) - except Exception as e: - self.theParent.makeAlert( - ["Could not write backup archive.",str(e)], - nwAlert.ERROR - ) - return False - - self.theParent.statusBar.setStatus("Project backup complete") - - return True - -# END Class NWBackup diff --git a/nw/project/project.py b/nw/project/project.py index cd3fdc55..fb4f340a 100644 --- a/nw/project/project.py +++ b/nw/project/project.py @@ -38,6 +38,7 @@ from lxml import etree from hashlib import sha256 from datetime import datetime from time import time +from shutil import make_archive from nw.tools import projectMaintenance, OptionState from nw.common import checkString, checkBool, checkInt @@ -478,6 +479,80 @@ class NWProject(): self.lockedBy = None return True + ## + # Backup Project + ## + + def zipIt(self, doNotify): + """Create a zip file of the entire project. + """ + + logger.info("Backing up project") + self.theParent.statusBar.setStatus("Backing up project ...") + + if self.mainConf.backupPath is None or self.mainConf.backupPath == "": + self.theParent.makeAlert(( + "Cannot backup project because no backup path is set. " + "Please set a valid backup location in Tools > Preferences." + ), nwAlert.WARN) + return False + + if self.projName is None or self.projName == "": + self.theParent.makeAlert(( + "Cannot backup project because no project name is set. " + "Please set a Working Title in Project > Project Settings." + ), nwAlert.WARN) + return False + + if not path.isdir(self.mainConf.backupPath): + self.theParent.makeAlert(( + "Cannot backup project because the backup path does not exist. " + "Please set a valid backup location in Tools > Preferences." + ), nwAlert.WARN) + return False + + cleanName = "" + for c in self.projName.strip(): + if c.isalpha() or c.isdigit() or c == " ": + cleanName += c + + baseDir = path.join(self.mainConf.backupPath, cleanName) + if not path.isdir(baseDir): + try: + mkdir(baseDir) + logger.debug("Created folder %s" % baseDir) + except Exception as e: + self.theParent.makeAlert( + ["Could not create backup folder.",str(e)], + nwAlert.ERROR + ) + return False + + archName = "Backup on %s" % datetime.now().strftime("%Y-%m-%d at %H.%M.%S") + baseName = path.join(baseDir, archName) + + try: + self._clearLockFile() + make_archive(baseName, "zip", self.projPath, ".") + self._writeLockFile() + if doNotify: + self.theParent.makeAlert( + "Backup archive file written to: '%s.zip'" % path.join(cleanName, archName), + nwAlert.INFO + ) + else: + logger.info("Backup written to: %s" % archName) + except Exception as e: + self.theParent.makeAlert( + ["Could not write backup archive.",str(e)], + nwAlert.ERROR + ) + return False + + self.theParent.statusBar.setStatus("Project backed up to '%s.zip'" % baseName) + + return True + ## # Setters ##