diff --git a/lib/galaxy/config/sample/datatypes_conf.xml.sample b/lib/galaxy/config/sample/datatypes_conf.xml.sample index 0e026b5a7b0a..a8e798143f8b 100644 --- a/lib/galaxy/config/sample/datatypes_conf.xml.sample +++ b/lib/galaxy/config/sample/datatypes_conf.xml.sample @@ -288,13 +288,14 @@ - - - - + + - + + + + diff --git a/lib/galaxy/datatypes/converters/archive_to_directory.xml b/lib/galaxy/datatypes/converters/archive_to_directory.xml new file mode 100644 index 000000000000..d0c56c154c09 --- /dev/null +++ b/lib/galaxy/datatypes/converters/archive_to_directory.xml @@ -0,0 +1,37 @@ + + + + galaxy-util + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/galaxy/datatypes/converters/tar_to_directory.xml b/lib/galaxy/datatypes/converters/tar_to_directory.xml index 59354b39b5fd..0160746283da 100644 --- a/lib/galaxy/datatypes/converters/tar_to_directory.xml +++ b/lib/galaxy/datatypes/converters/tar_to_directory.xml @@ -1,7 +1,7 @@ - galaxy-util + galaxy-util mkdir '$output1.files_path'; diff --git a/lib/galaxy/datatypes/data.py b/lib/galaxy/datatypes/data.py index 0024adea6155..e5fea2612ace 100644 --- a/lib/galaxy/datatypes/data.py +++ b/lib/galaxy/datatypes/data.py @@ -465,6 +465,7 @@ def _serve_file_download(self, headers, data, trans, to_ext, file_size, **kwd): composite_extensions = trans.app.datatypes_registry.get_composite_extensions() composite_extensions.append("html") # for archiving composite datatypes composite_extensions.append("data_manager_json") # for downloading bundles if bundled. + composite_extensions.append("directory") # for downloading directories. if data.extension in composite_extensions: return self._archive_composite_dataset(trans, data, headers, do_action=kwd.get("do_action", "zip")) @@ -1212,6 +1213,18 @@ def regex_line_dataprovider( class Directory(Data): """Class representing a directory of files.""" + file_ext = "directory" + + def _archive_main_file( + self, archive: ZipstreamWrapper, display_name: str, data_filename: str + ) -> Tuple[bool, str, str]: + """Overwrites the method to not do anything. + + No main file gets added to a directory archive. + """ + error, msg, messagetype = False, "", "" + return error, msg, messagetype + class GenericAsn1(Text): """Class for generic ASN.1 text format""" diff --git a/lib/galaxy_test/api/test_tools_upload.py b/lib/galaxy_test/api/test_tools_upload.py index ed32bc92caf0..57ffece984dc 100644 --- a/lib/galaxy_test/api/test_tools_upload.py +++ b/lib/galaxy_test/api/test_tools_upload.py @@ -2,12 +2,14 @@ import os import urllib.parse from base64 import b64encode +from typing import cast import pytest from tusclient import client from galaxy.tool_util.verify.test_data import TestDataResolver from galaxy.util import UNKNOWN +from galaxy.util.compression_utils import decompress_bytes_to_directory from galaxy.util.unittest_utils import ( skip_if_github_down, skip_if_site_down, @@ -639,6 +641,44 @@ def test_upload_composite_from_bad_tar(self, history_id): details = self.dataset_populator.get_history_dataset_details(history_id, dataset=dataset, assert_ok=False) assert details["state"] == "error" + def test_upload_zip_directory_roundtrip(self, history_id): + testdir = TestDataResolver().get_filename("testdir1.zip") + with open(testdir, "rb") as fh: + details = self._upload_and_get_details(fh, api="fetch", history_id=history_id, ext="zip", assert_ok=True) + assert details["file_ext"] == "zip" + + # Convert/unpack zip to directory. + payload = { + "src": "hda", + "id": details["id"], + "source_type": "zip", + "target_type": "directory", + "history_id": history_id, + } + create_response = self._post("tools/CONVERTER_archive_to_directory/convert", data=payload) + self.dataset_populator.wait_for_job(create_response.json()["jobs"][0]["id"], assert_ok=True) + create_response.raise_for_status() + directory_dataset = create_response.json()["outputs"][0] + + # Download (compressed) directory and verify structure. + content = self.dataset_populator.get_history_dataset_content( + history_id, dataset=directory_dataset, to_ext="directory", type="bytes" + ) + dir_path = decompress_bytes_to_directory(cast(bytes, content)) + expected_directory_structure = { + "file1": "File", + "file2": "File", + "dir1": "Directory", + "dir1/file3": "File", + } + assert dir_path.endswith("testdir1") + for path, entry_class in expected_directory_structure.items(): + path = os.path.join(dir_path, path) + if entry_class == "Directory": + assert os.path.isdir(path) + else: + assert os.path.isfile(path) + def test_upload_dbkey(self): with self.dataset_populator.test_history() as history_id: payload = self.dataset_populator.upload_payload(history_id, "Test123", dbkey="hg19")