Source code for ccpn.core.lib.ProjectArchiver

"""
Code for Project archiving
Replaced former Api.packageProject and _unpackCcpnTarfile
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (https://www.ccpn.ac.uk) 2014 - 2022"
__credits__ = ("Ed Brooksbank, Joanna Fox, Victoria A Higman, Luca Mureddu, Eliza Płoskoń",
               "Timothy J Ragan, Brian O Smith, Gary S Thompson & Geerten W Vuister")
__licence__ = ("CCPN licence. See https://ccpn.ac.uk/software/licensing/")
__reference__ = ("Skinner, S.P., Fogh, R.H., Boucher, W., Ragan, T.J., Mureddu, L.G., & Vuister, G.W.",
                 "CcpNmr AnalysisAssign: a flexible platform for integrated NMR analysis",
                 "J.Biomol.Nmr (2016), 66, 111-124, http://doi.org/10.1007/s10858-016-0060-y")
#=========================================================================================
# Last code modification
#=========================================================================================
__modifiedBy__ = "$modifiedBy: Ed Brooksbank $"
__dateModified__ = "$dateModified: 2022-02-02 12:59:27 +0000 (Wed, February 02, 2022) $"
__version__ = "$Revision: 3.1.0 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: gvuister $"
__date__ = "$Date: 2022-01-18 10:28:48 +0000 (Tue, January 18, 2022) $"
#=========================================================================================
# Start of code
#=========================================================================================

import os
from tarfile import TarFile

from ccpn.util.Path import Path, aPath
from ccpn.util.Logging import getLogger

from ccpn.framework.PathsAndUrls import \
    CCPN_DIRECTORY_SUFFIX, \
    CCPN_API_DIRECTORY, \
    CCPN_STATE_DIRECTORY, \
    CCPN_ARCHIVES_DIRECTORY, \
    CCPN_SUMMARIES_DIRECTORY, \
    CCPN_LOGS_DIRECTORY, \
    CCPN_PLUGINS_DIRECTORY, \
    CCPN_SCRIPTS_DIRECTORY


_directoriesToArchive = (
    CCPN_API_DIRECTORY,
    CCPN_STATE_DIRECTORY,
    CCPN_SUMMARIES_DIRECTORY,
    CCPN_LOGS_DIRECTORY,
    CCPN_PLUGINS_DIRECTORY,
    CCPN_SCRIPTS_DIRECTORY
    )

ARCHIVE_SUFFIX = '.tgz'


[docs]class ProjectArchiver(object): """A class to manage the archives of a project """ def __init__(self, projectPath): """Initialise the class with the specified projectPath """ # check that the specified projectPath is valid type and exists if projectPath is None or not isinstance(projectPath, (str, Path)): raise ValueError(f'Invalid projectPath: expected str or Path instance but got {projectPath}') self._projectPath = aPath(projectPath) if not self._projectPath.exists(): raise ValueError(f'Invalid projectPath: {projectPath} does not exist') @property def archiveDirectory(self) -> Path: """:return: absolute path to directory with archives as a Path instance """ return self._projectPath.fetchDir(CCPN_ARCHIVES_DIRECTORY) @property def archives(self) -> list: """:return: a list of archive (.tgz) tar files""" return self.archiveDirectory.listdir(suffix=ARCHIVE_SUFFIX)
[docs] def makeArchive(self) -> Path: """Make a new time-stamped archive from project. :return: absolute path to the new archives as a Path instance or None on IOerror """ archivePath = self.archiveDirectory / self._projectPath.basename archivePath = archivePath.addTimeStamp().withSuffix( CCPN_DIRECTORY_SUFFIX + ARCHIVE_SUFFIX) cwd = os.getcwd() try: os.chdir(self._projectPath) with TarFile.open(archivePath, mode='w:gz') as tarfile: for _dir in _directoriesToArchive: # check that the directory exists and create as required self._projectPath.fetchDir(_dir) # add the folder as relative path tarfile.add(_dir) except IOError as es: getLogger().error(f'ProjectArchiver.makeArchive: unable to complete archive {archivePath}') getLogger().debug(f'ProjectArchiver.makeArchive: {es}') archivePath = None finally: os.chdir(cwd) return archivePath
[docs] def restoreArchive(self, archivePath) -> Path: """Restore project from archivePath. :return: the path to the restored project or None on IOerror """ if archivePath is None or not isinstance(archivePath, (str, Path)): raise ValueError(f'Invalid archivePath: expected str or Path instance but got {archivePath}') archivePath = aPath(archivePath) if not archivePath.exists(): raise ValueError(f'Invalid archivePath: {archivePath} does not exist') cwd = os.getcwd() # we unpack in the parent directory of the current project # the restored project will derive its name from the archive tar-file, without the .tgz suffix _restoredPath = (self._projectPath.parent / archivePath.name).withoutSuffix() if not _restoredPath.exists(): _restoredPath.mkdir() os.chdir(_restoredPath) try: with TarFile.open(archivePath, mode='r') as tarfile: tarfile.extractall() except IOError: getLogger().error(f'ProjectArchiver.restoreArchive: unable to extract archive {archivePath}') _restoredPath = None finally: os.chdir(cwd) return _restoredPath
def __str__(self): return f'<ProjectArchiver: {self._projectPath}>'