diff --git a/delivery/models/runfolder.py b/delivery/models/runfolder.py index 0bcf9e5..3a95458 100644 --- a/delivery/models/runfolder.py +++ b/delivery/models/runfolder.py @@ -41,7 +41,8 @@ def __init__( self, file_path, base_path=None, - file_checksum=None + file_checksum=None, + file_operation="symlink" ): """ A `RunfolderFile` object representing a file in the runfolder @@ -54,11 +55,15 @@ def __init__( :param file_path: the path to the file :param base_path: a path relative to which the file will be considered :param file_checksum: a computed checksum for the file + :param file_operation: the file operation to perform, symlink (default) or copy """ self.file_path = os.path.abspath(file_path) self.file_name = os.path.basename(file_path) self.base_path = base_path or os.path.dirname(self.file_path) self.checksum = file_checksum + self.file_operation = file_operation \ + if file_operation and file_operation in ["symlink", "copy"] \ + else "symlink" @classmethod def create_object_from_path( @@ -68,7 +73,8 @@ def create_object_from_path( filesystem_service, metadata_service, base_path=None, - checksums=None + checksums=None, + file_operation="symlink" ): checksums = checksums or {} relative_file_path = filesystem_service.relpath( @@ -83,5 +89,6 @@ def create_object_from_path( return cls( file_path, base_path=base_path, - file_checksum=checksum + file_checksum=checksum, + file_operation=file_operation ) diff --git a/delivery/models/sample.py b/delivery/models/sample.py index 6f3b2b1..d823e5c 100644 --- a/delivery/models/sample.py +++ b/delivery/models/sample.py @@ -47,7 +47,9 @@ def __init__( read_no=None, is_index=None, base_path=None, - checksum=None): + checksum=None, + file_operation="symlink" + ): """ A `SampleFile` object @@ -64,11 +66,13 @@ def __init__( :param is_index: if True, the sequence file contains index sequences :param base_path: a path relative to which the file will be considered :param checksum: the MD5 checksum for this SampleFile + :param file_operation: the file operation to perform, symlink (default) or copy """ super(SampleFile, self).__init__( sample_path, base_path=base_path, - file_checksum=checksum + file_checksum=checksum, + file_operation=file_operation ) self.sample_name = sample_name self.sample_index = sample_index @@ -88,4 +92,6 @@ def __hash__(self): self.lane_no, self.read_no, self.is_index, - self.checksum)) + self.checksum, + self.file_operation + )) diff --git a/delivery/repositories/project_repository.py b/delivery/repositories/project_repository.py index 97bbb68..a89478f 100644 --- a/delivery/repositories/project_repository.py +++ b/delivery/repositories/project_repository.py @@ -324,7 +324,8 @@ def get_project_readme( filesystem_service=self.filesystem_service, metadata_service=self.metadata_service, base_path=self.filesystem_service.dirname(readme_file), - checksums=checksums + checksums=checksums, + file_operation="copy" ) ] except FileNotFoundError: diff --git a/delivery/services/file_system_service.py b/delivery/services/file_system_service.py index 86bb4a6..de2470c 100644 --- a/delivery/services/file_system_service.py +++ b/delivery/services/file_system_service.py @@ -1,6 +1,7 @@ import os import logging +import shutil log = logging.getLogger(__name__) @@ -91,6 +92,19 @@ def symlink(self, source, link_name): self.makedirs(self.dirname(link_name), exist_ok=True) return os.symlink(source, link_name) + @staticmethod + def copy(source, dest): + """ + Shadows shutil.copyfile + :param source: + :param dest: + :return: None + """ + try: + return shutil.copyfile(source, dest) + except IsADirectoryError: + return shutil.copytree(source, dest, symlinks=True) + @staticmethod def mkdir(path): """ diff --git a/delivery/services/organise_service.py b/delivery/services/organise_service.py index 4d75407..7a2d169 100644 --- a/delivery/services/organise_service.py +++ b/delivery/services/organise_service.py @@ -128,10 +128,11 @@ def organise_project(self, runfolder, project, organised_projects_path, lanes): def organise_project_file(self, project_file, organised_project_path): """ - Find and symlink the project report to the organised project directory. + Find and symlink or copy the project-associated files to the organised project directory. - :param project: a Project instance representing the project before organisation - :param organised_project: a Project instance representing the project after organisation + :param project_file: a RunfolderFile instance representing the project-associated file + before organisation + :param organised_project_path: path where the project will be organised """ # the relative path from the project file base to the project file (e.g. plots/filename.png) @@ -145,11 +146,16 @@ def organise_project_file(self, project_file, organised_project_path): organised_project_path, relpath ) - # the relative path from the symlink to the original file - link_path = self.file_system_service.relpath( - project_file.file_path, - self.file_system_service.dirname(link_name)) - self.file_system_service.symlink(link_path, link_name) + if project_file.file_operation == "copy": + self.file_system_service.copy(project_file.file_path, link_name) + else: + # the relative path from the symlink to the original file + link_path = self.file_system_service.relpath( + project_file.file_path, + self.file_system_service.dirname(link_name) + ) + self.file_system_service.symlink(link_path, link_name) + return RunfolderFile( link_name, base_path=organised_project_path, @@ -203,8 +209,15 @@ def organise_sample_file(self, sample_file, organised_sample_path, lanes): # create the symlink in the supplied directory and relative to the file's original location link_name = os.path.join(organised_sample_path, sample_file.file_name) - relative_path = self.file_system_service.relpath(sample_file.file_path, organised_sample_path) - self.file_system_service.symlink(relative_path, link_name) + if sample_file.file_operation == "copy": + self.file_system_service.copy(sample_file.file_path, link_name) + else: + relative_path = self.file_system_service.relpath( + sample_file.file_path, + organised_sample_path + ) + self.file_system_service.symlink(relative_path, link_name) + return SampleFile( sample_path=link_name, sample_name=sample_file.sample_name,