diff --git a/novelwriter/core/document.py b/novelwriter/core/document.py index cf0e1f15..a93d8855 100644 --- a/novelwriter/core/document.py +++ b/novelwriter/core/document.py @@ -31,7 +31,7 @@ from typing import TYPE_CHECKING from pathlib import Path from novelwriter.enum import nwItemLayout, nwItemClass -from novelwriter.error import formatException +from novelwriter.error import formatException, logException from novelwriter.common import formatTimeStamp, isHandle from novelwriter.core.item import NWItem @@ -106,7 +106,28 @@ class NWDocument: return self._item ## - # Class Methods + # Static Methods + ## + + @staticmethod + def quickReadText(content: Path, tHandle: str) -> str: + """Return the text of a document in a fast and efficient way.""" + if (path := content / f"{tHandle}.nwd").is_file(): + try: + with open(path, mode="r", encoding="utf-8") as inFile: + line = "" + for _ in range(10): + if not (line := inFile.readline()).startswith(r"%%~"): + break + return line + inFile.read() + except Exception: + logger.error("Cannot read document with handle '%s'", tHandle) + logException() + return "" + return "" + + ## + # Methods ## def fileExists(self) -> bool: @@ -155,7 +176,7 @@ class NWDocument: try: with open(docPath, mode="r", encoding="utf-8") as inFile: # Check the first <= 10 lines for metadata - for i in range(10): + for _ in range(10): line = inFile.readline() if line.startswith(r"%%~"): self._parseMeta(line) diff --git a/novelwriter/core/storage.py b/novelwriter/core/storage.py index 59ea24b3..e8a2bb16 100644 --- a/novelwriter/core/storage.py +++ b/novelwriter/core/storage.py @@ -289,21 +289,8 @@ class NWStorage: def getDocumentText(self, tHandle: str) -> str: """Return the text of a document in a fast and efficient way.""" - if ( - isinstance(self._runtimePath, Path) - and (path := self._runtimePath / "content" / f"{tHandle}.nwd").is_file() - ): - try: - with open(path, mode="r", encoding="utf-8") as inFile: - line = "" - for _ in range(10): - if not (line := inFile.readline()).startswith(r"%%~"): - break - return line + inFile.read() - except Exception: - logger.error("Cannot read document with handle '%s'", tHandle) - logException() - return "" + if isinstance(self._runtimePath, Path): + return NWDocument.quickReadText(self._runtimePath / "content", tHandle) return "" def scanContent(self) -> list[str]: diff --git a/tests/test_core/test_core_document.py b/tests/test_core/test_core_document.py index 5a236afc..3dbdd454 100644 --- a/tests/test_core/test_core_document.py +++ b/tests/test_core/test_core_document.py @@ -47,6 +47,9 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd): assert bool(doc) is False assert doc.readDocument() is None assert doc.fileExists() is False + assert doc.hashError is False + assert doc.createdDate == "Unknown" + assert doc.updatedDate == "Unknown" # Non-existent handle doc = NWDocument(project, C.hInvalid) @@ -76,6 +79,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd): # Try to open a new (non-existent) file xHandle = project.newFile("New File", C.hNovelRoot) + assert xHandle is not None doc = NWDocument(project, xHandle) assert bool(doc) is True assert repr(doc) == f"" @@ -93,7 +97,6 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd): # Set handle and save text = "### Test File\n\nText ...\n\n" doc = NWDocument(project, xHandle) - assert doc.readDocument(xHandle) == "" # type: ignore assert doc.writeDocument(text) is True # Save again to ensure temp file and previous file is handled @@ -145,6 +148,21 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd): doc._handle = None assert doc.writeDocument(text) is False + # Quick Read + # ========== + + contPath = fncPath / "content" + assert NWDocument.quickReadText(contPath, xHandle) == ( + "### Test File\n\n" + "Text ...\n\n" + ) + + # Check read text fallback + assert NWDocument.quickReadText(contPath, "0000000000000") == "" + with monkeypatch.context() as mp: + mp.setattr("builtins.open", causeOSError) + assert NWDocument.quickReadText(contPath, xHandle) == "" + # Delete Document # =============== diff --git a/tests/test_core/test_core_storage.py b/tests/test_core/test_core_storage.py index 6ea0937d..76520078 100644 --- a/tests/test_core/test_core_storage.py +++ b/tests/test_core/test_core_storage.py @@ -64,6 +64,7 @@ def testCoreStorage_CreateNewProject(mockGUI, fncPath): assert bool(storage.getDocument(C.hSceneDoc)) is False assert storage.getMetaFile("file") is None assert storage.scanContent() == [] + assert storage.getDocumentText(C.hSceneDoc) == "" # Cannot prepare a non-empty folder (fncPath / "foobar.txt").touch() @@ -160,12 +161,6 @@ def testCoreStorage_InitProjectStorage(monkeypatch, mockGUI, fncPath, mockRnd): # We can directly access the content of a document assert storage.getDocumentText(C.hSceneDoc) == "### New Scene\n\n" - # Check read text fallback - assert storage.getDocumentText(C.hInvalid) == "" - with monkeypatch.context() as mp: - mp.setattr("builtins.open", causeOSError) - assert storage.getDocumentText(C.hSceneDoc) == "" - project.closeProject() # END Test testCoreStorage_InitProjectStorage