@@ -1,7 +1,6 @@
"""
novelWriter – Project Storage Class
===================================
The main class handling the project storage
File History:
Created: 2022-11-01 [2.0rc2] NWStorage
@@ -22,10 +21,12 @@ 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 <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import logging
from time import time
from typing import TYPE_CHECKING
from pathlib import Path
from zipfile import ZIP_DEFLATED , ZIP_STORED , ZipFile
@@ -36,29 +37,32 @@ from novelwriter.constants import nwFiles
from novelwriter . core . document import NWDocument
from novelwriter . core . projectxml import ProjectXMLReader , ProjectXMLWriter
if TYPE_CHECKING : # pragma: no cover
from novelwriter . core . project import NWProject
logger = logging . getLogger ( __name__ )
class NWStorage :
""" Core: Project Storage Class
The class that handles all paths related to the project storage.
"""
MODE_INACTIVE = 0
MODE_INPLACE = 1
MODE_ARCHIVE = 2
def __init__ ( self , theProject ) :
self . theProject = theProject
def __init__ ( self , project : NWProject ) :
self . _project = project
self . _storagePath = None
self . _runtimePath = None
self . _lockFilePath = None
self . _openMode = self . MODE_INACTIVE
return
def clear ( self ) :
""" Reset internal variables.
"""
""" Reset internal variables. """
self . _storagePath = None
self . _runtimePath = None
self . _openMode = self . MODE_INACTIVE
@@ -69,15 +73,17 @@ class NWStorage:
##
@property
def storagePath ( self ) :
def storagePath ( self ) - > Path | None :
""" Get the path where the project is saved. """
return self . _storagePath
@property
def runtimePath ( self ) :
def runtimePath ( self ) - > Path | None :
""" Get the path where the project is saved at runtime. """
return self . _runtimePath
@property
def contentPath ( self ) :
def contentPath ( self ) - > Path | None :
""" Return the path used for project content. The folder must
already exist, otherwise this property is None.
"""
@@ -94,12 +100,11 @@ class NWStorage:
# Core Methods
##
def isOpen ( self ) :
""" Check if the storage location is open.
"""
def isOpen ( self ) - > bool :
""" Check if the storage location is open. """
return self . _runtimePath is not None
def openProjectInPlace ( self , path , newProject = False ) :
def openProjectInPlace ( self , path : str | Path , newProject : bool = False ) - > bool :
""" Open a novelWriter project in-place. That is, it is opened
directly from a project folder.
"""
@@ -124,13 +129,15 @@ class NWStorage:
return True
def openProjectArchive ( self , path ) : # pragma: no cover
""" Placeholder for later implementation. See #977 .
def openProjectArchive ( self , path : str | Path ) - > bool : # pragma: no cover
""" Open the project from a single file .
Placeholder for later implementation. See #977.
"""
return False
def runPostSaveTasks ( self , autoSave = False ) : # pragma: no cover
def runPostSaveTasks ( self , autoSave : bool = False ) - > bool : # pragma: no cover
""" Run tasks after the project has been saved.
Placeholder for later implementation. See #977.
"""
if self . _openMode == self . MODE_INPLACE :
# Nothing to do, so we just return
@@ -139,8 +146,7 @@ class NWStorage:
return True
def closeSession ( self ) :
""" Run tasks related to closing the session.
"""
""" Run tasks related to closing the session. """
# Clear lockfile
self . clear ( )
return
@@ -149,51 +155,35 @@ class NWStorage:
# Content Access Methods
##
def getXmlReader ( self ) :
""" Return a properly configured ProjectXMLReader instance.
"""
def getXmlReader ( self ) - > ProjectXMLReader | None :
""" Return a properly configured ProjectXMLReader instance. """
if self . _runtimePath is None :
return None
projFile = self . _runtimePath / nwFiles . PROJ_FILE
xmlReader = ProjectXMLReader ( projFile )
return xmlReader
def getXmlWriter ( self ) :
""" Return a properly configured ProjectXMLWriter instance.
"""
def getXmlWriter ( self ) - > ProjectXMLWriter | None :
""" Return a properly configured ProjectXMLWriter instance. """
if self . _runtimePath is None :
return None
xmlWriter = ProjectXMLWriter ( self . _runtimePath )
return xmlWriter
def getDocument ( self , tHandle ) :
""" Return a document wrapper object.
"""
def getDocument ( self , tHandle : str ) - > NWDocument :
""" Return a document wrapper object. """
if self . _runtimePath is not None :
return NWDocument ( self . theProject , tHandle )
return NWDocument ( self . theProject , None )
return NWDocument ( self . _project , tHandle )
return NWDocument ( self . _project , None )
def getMetaFile ( self , fileName ) :
""" Return the path to a file in the project meta folder.
"""
def getMetaFile ( self , fileName : str ) - > Path | None :
""" Return the path to a file in the project meta folder. """
if self . _runtimePath is not None :
return self . _runtimePath / " meta " / fileName
return None
def getCacheFile ( self , fileName ) :
""" Return the path to a file in the project cache folder.
"""
if self . _runtimePath is not None :
return self . _runtimePath / " cache " / fileName
return None
def readLockFile ( self ) :
""" Read the project lock file.
"""
def readLockFile ( self ) - > list :
""" Read the project lock file. """
if self . _lockFilePath is None :
return [ " ERROR " ]
@@ -212,9 +202,8 @@ class NWStorage:
return lines
def writeLockFile ( self ) :
""" Write the project lock file.
"""
def writeLockFile ( self ) - > bool :
""" Write the project lock file. """
if self . _lockFilePath is None :
return False
@@ -231,9 +220,8 @@ class NWStorage:
return True
def clearLockFile ( self ) :
""" Remove the lock file, if it exists.
"""
def clearLockFile ( self ) - > bool :
""" Remove the lock file, if it exists. """
if self . _lockFilePath is None :
return False
@@ -247,7 +235,7 @@ class NWStorage:
return True
def zipIt ( self , target , compression = None ) :
def zipIt ( self , target : str | Path , compression : int | None = None ) - > bool :
""" Zip the content of the project at its runtime location into a
zip file. This process will only grab files that are supposed to
be in the project. All non-project files will be left out.
@@ -291,9 +279,8 @@ class NWStorage:
# Internal Functions
##
def _prepareStorage ( self , checkLegacy = True , newProject = False ) :
""" Prepare the storage area for the project.
"""
def _prepareStorage ( self , checkLegacy : bool = True , newProject : bool = False ) - > bool :
""" Prepare the storage area for the project. """
path = self . _runtimePath
if not isinstance ( path , Path ) :
logger . error ( " No path set " )
@@ -318,7 +305,6 @@ class NWStorage:
try :
path . mkdir ( exist_ok = True )
( path / " content " ) . mkdir ( exist_ok = True )
( path / " cache " ) . mkdir ( exist_ok = True )
( path / " meta " ) . mkdir ( exist_ok = True )
except Exception as exc :
logger . error ( " Failed to create required project folders " , exc_info = exc )
@@ -385,8 +371,7 @@ class NWStorage:
return
def _deleteDeprecatedFiles ( self , path : Path ) :
""" Delete files that are no longer used by novelWriter.
"""
""" Delete files that are no longer used by novelWriter. """
remove = [
path / " meta " / " mainOptions.json " , # Replaced in 0.5
path / " meta " / " exportOptions.json " , # Replaced in 0.5
@@ -394,16 +379,20 @@ class NWStorage:
path / " meta " / " timelineOptions.json " , # Replaced in 0.5
path / " meta " / " docMergeOptions.json " , # Replaced in 0.5
path / " meta " / " sessionLogOptions.json " , # Replaced in 0.5
path / " cache " / " prevBuild.json " , # Dropped in 2.1 Beta 1
path / " cache " , # Dropped in 2.1 Beta 1
path / " ToC.json " , # Dropped in 1.0 RC 1
]
for item in remove :
if item . is_file ( ) :
if item . exists ( ) :
try :
item . unlink ( )
if item . is_dir ( ) :
item . rmdir ( )
else :
item . unlink ( )
logger . info ( " Deleted: %s " , item )
except Exception as exc :
logger . warning ( " Failed to delete: %s " , item , exc_info = exc )
return
# END Class NWStorage