def deleteItem(self, tHandle=None, alreadyAsked=False, askForTrash=False): """Delete an item from the project tree. As a first step, files are moved to the Trash folder. Permanent deletion is a second step. This second step also deletes the item from the project object as well as delete the files on disk. Folders are deleted if they're empty only, and the deletion is always permanent. """ if not self.theParent.hasProject: logger.error("No project open") return False if tHandle is None: tHandle = self.getSelectedHandle() if tHandle is None: return False trItemS = self._getTreeItem(tHandle) nwItemS = self.theProject.projTree[tHandle] if trItemS is None or nwItemS is None: return False wCount = int(trItemS.data(self.C_COUNT, Qt.UserRole)) if nwItemS.itemType == nwItemType.FILE: logger.debug("User requested file %s moved to trash" % tHandle) trItemP = trItemS.parent() trItemT = self._addTrashRoot() if trItemP is None or trItemT is None: logger.error("Could not delete item") return False pHandle = nwItemS.itemParent if self.theProject.projTree.isTrashRoot(pHandle): # If the file is in the trash folder already, as the # user if they want to permanently delete the file. doPermanent = False if not alreadyAsked: msgYes = self.theParent.askQuestion( "Delete File", "Permanently delete file '%s'?" % nwItemS.itemName ) if msgYes: doPermanent = True else: doPermanent = True if doPermanent: logger.debug("Permanently deleting file with handle %s" % tHandle) self.propagateCount(tHandle, 0) tIndex = trItemP.indexOfChild(trItemS) trItemC = trItemP.takeChild(tIndex) if self.theParent.docEditor.theHandle == tHandle: self.theParent.closeDocument() theDoc = NWDoc(self.theProject, self.theParent) theDoc.deleteDocument(tHandle) del self.theProject.projTree[tHandle] self.theIndex.deleteHandle(tHandle) else: # The file is not already in the trash folder, so we # move it there. doTrash = False if askForTrash: msgYes = self.theParent.askQuestion( "Delete File", "Move file '%s' to Trash?" % nwItemS.itemName ) if msgYes: doTrash = True else: doTrash = True if doTrash: if pHandle is None: logger.warning("File has no parent item") self.propagateCount(tHandle, 0) tIndex = trItemP.indexOfChild(trItemS) trItemC = trItemP.takeChild(tIndex) trItemT.addChild(trItemC) nwItemS.setParent(self.theProject.projTree.trashRoot()) self.propagateCount(tHandle, wCount) self._setTreeChanged(True) self.theIndex.deleteHandle(tHandle) elif nwItemS.itemType == nwItemType.FOLDER: logger.debug("User requested folder %s deleted" % tHandle) trItemP = trItemS.parent() if trItemP is None: logger.error("Could not delete folder") return False tIndex = trItemP.indexOfChild(trItemS) if trItemS.childCount() == 0: trItemP.takeChild(tIndex) del self.theProject.projTree[tHandle] else: self.makeAlert(( "Cannot delete folder. It is not empty. " "Recursive deletion is not supported. " "Please delete the content first." ), nwAlert.ERROR) return False elif nwItemS.itemType == nwItemType.ROOT: logger.debug("User requested root folder %s deleted" % tHandle) tIndex = self.indexOfTopLevelItem(trItemS) if trItemS.childCount() == 0: self.takeTopLevelItem(tIndex) del self.theProject.projTree[tHandle] self.theParent.mainMenu.setAvailableRoot() self._setTreeChanged(True) else: self.makeAlert(( "Cannot delete root folder. It is not empty. " "Recursive deletion is not supported. " "Please delete the content first." ), nwAlert.ERROR) return False return True
def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal): """Test loading and saving a document with the NWDoc class. """ theProject = NWProject(dummyGUI) assert theProject.openProject(nwMinimal) assert theProject.projPath == nwMinimal theDoc = NWDoc(theProject, dummyGUI) sHandle = "8c659a11cd429" # Not a valid handle assert theDoc.openDocument("dummy") is None # Non-existent handle assert theDoc.openDocument("0000000000000") is None # Cause open() to fail while loading def dummyOpen(*args, **kwargs): raise OSError with monkeypatch.context() as mp: mp.setattr("builtins.open", dummyOpen) assert theDoc.openDocument(sHandle) is None # Load the text assert theDoc.openDocument(sHandle) == "### New Scene\n\n" # Try to open a new (non-existent) file nHandle = theProject.projTree.findRoot(nwItemClass.NOVEL) assert nHandle is not None xHandle = theProject.newFile("New File", nwItemClass.NOVEL, nHandle) assert theDoc.openDocument(xHandle) == "" # Check cached item assert isinstance(theDoc._theItem, NWItem) assert theDoc.openDocument(xHandle, isOrphan=True) == "" assert theDoc._theItem is None # Set handle and save again theText = "### Test File\n\nText ...\n\n" assert theDoc.openDocument(xHandle) == "" assert theDoc.saveDocument(theText) # Save again to ensure temp file and previous file is handled assert theDoc.saveDocument(theText) # Check file content docPath = os.path.join(nwMinimal, "content", xHandle + ".nwd") with open(docPath, mode="r", encoding="utf8") as inFile: assert inFile.read() == ("%%~name: New File\n" f"%%~path: a508bb932959c/{xHandle}\n" "%%~kind: NOVEL/SCENE\n" "### Test File\n\n" "Text ...\n\n") # Force no meta data theDoc._theItem = None assert theDoc.saveDocument(theText) with open(docPath, mode="r", encoding="utf8") as inFile: assert inFile.read() == theText # Cause open() to fail while saving with monkeypatch.context() as mp: mp.setattr("builtins.open", causeOSError) assert not theDoc.saveDocument(theText) # Saving with no handle theDoc.clearDocument() assert not theDoc.saveDocument(theText) # Delete the last document assert not theDoc.deleteDocument("dummy") assert os.path.isfile(docPath) # Cause the delete to fail with monkeypatch.context() as mp: mp.setattr("os.unlink", causeOSError) assert not theDoc.deleteDocument(xHandle) # Make the delete pass assert theDoc.deleteDocument(xHandle) assert not os.path.isfile(docPath)