diff --git a/ckanext/unfold/adapters/_7z.py b/ckanext/unfold/adapters/_7z.py index da6c6a8..77951a7 100644 --- a/ckanext/unfold/adapters/_7z.py +++ b/ckanext/unfold/adapters/_7z.py @@ -17,7 +17,9 @@ log = logging.getLogger(__name__) -def build_directory_tree(filepath: str, remote: Optional[bool] = False): +def build_directory_tree( + filepath: str, resource_view: dict[str, Any], remote: Optional[bool] = False +): try: if remote: file_list = get7zlist_from_url(filepath) diff --git a/ckanext/unfold/adapters/gzip.py b/ckanext/unfold/adapters/gzip.py index 066c148..94cbcca 100644 --- a/ckanext/unfold/adapters/gzip.py +++ b/ckanext/unfold/adapters/gzip.py @@ -8,7 +8,9 @@ import ckanext.unfold.utils as unf_utils -def build_directory_tree(filepath: str, remote: Optional[bool] = False): +def build_directory_tree( + filepath: str, resource_view: dict[str, Any], remote: Optional[bool] = False +): resource = _get_resource(filepath) return [_build_node(resource)] diff --git a/ckanext/unfold/adapters/rar.py b/ckanext/unfold/adapters/rar.py index 10a585e..2ba9223 100644 --- a/ckanext/unfold/adapters/rar.py +++ b/ckanext/unfold/adapters/rar.py @@ -20,17 +20,19 @@ def build_directory_tree( - filepath: str, remote: Optional[bool] = False + filepath: str, resource_view: dict[str, Any], remote: Optional[bool] = False ) -> list[unf_types.Node]: try: if remote: file_list = get_rarlist_from_url(filepath) else: with rarfile.RarFile(filepath) as archive: - if archive.needs_password(): + if archive.needs_password() and not resource_view.get("archive_pass"): raise unf_exception.UnfoldError( "Error. Archive is protected with password" ) + elif archive.needs_password(): + archive.setpassword(resource_view["archive_pass"]) file_list: list[RarInfo] = archive.infolist() except RarError as e: @@ -38,6 +40,11 @@ def build_directory_tree( except requests.RequestException as e: raise unf_exception.UnfoldError(f"Error fetching remote archive: {e}") + if not file_list: + raise unf_exception.UnfoldError( + "Error. The archive is either empty or the password is incorrect." + ) + nodes: list[unf_types.Node] = [] for entry in file_list: diff --git a/ckanext/unfold/adapters/rpm.py b/ckanext/unfold/adapters/rpm.py index f1270db..45e398a 100644 --- a/ckanext/unfold/adapters/rpm.py +++ b/ckanext/unfold/adapters/rpm.py @@ -14,7 +14,9 @@ log = logging.getLogger(__name__) -def build_directory_tree(filepath: str, remote: Optional[bool] = False): +def build_directory_tree( + filepath: str, resource_view: dict[str, Any], remote: Optional[bool] = False +): try: if remote: file_list = get_rpmlist_from_url(filepath) diff --git a/ckanext/unfold/adapters/tar.py b/ckanext/unfold/adapters/tar.py index b7a973d..cfe9a7e 100644 --- a/ckanext/unfold/adapters/tar.py +++ b/ckanext/unfold/adapters/tar.py @@ -19,7 +19,10 @@ def build_directory_tree( - filepath: str, remote: Optional[bool] = False, compression: Optional[str] = None + filepath: str, + resource_view: dict[str, Any], + remote: Optional[bool] = False, + compression: Optional[str] = None, ): mode = "r" if not compression else f"r:{compression}" diff --git a/ckanext/unfold/adapters/zip.py b/ckanext/unfold/adapters/zip.py index 2025f03..382a93d 100644 --- a/ckanext/unfold/adapters/zip.py +++ b/ckanext/unfold/adapters/zip.py @@ -18,7 +18,7 @@ def build_directory_tree( - filepath: str, remote: Optional[bool] = False + filepath: str, resource_view: dict[str, Any], remote: Optional[bool] = False ) -> list[unf_types.Node]: try: if remote: diff --git a/ckanext/unfold/assets/js/unfold-init-jstree.js b/ckanext/unfold/assets/js/unfold-init-jstree.js index bdb0874..19c97b7 100644 --- a/ckanext/unfold/assets/js/unfold-init-jstree.js +++ b/ckanext/unfold/assets/js/unfold-init-jstree.js @@ -4,6 +4,7 @@ ckan.module("unfold-init-jstree", function ($, _) { options: { data: null, resourceId: null, + resourceViewId: null }, initialize: function () { @@ -17,7 +18,7 @@ ckan.module("unfold-init-jstree", function ($, _) { $.ajax({ url: this.sandbox.url("/api/action/get_archive_structure"), - data: { "id": this.options.resourceId }, + data: { "id": this.options.resourceId, "view_id": this.options.resourceViewId }, success: this._onSuccessRequest }); }, diff --git a/ckanext/unfold/logic/action.py b/ckanext/unfold/logic/action.py index 8929244..8da3d96 100644 --- a/ckanext/unfold/logic/action.py +++ b/ckanext/unfold/logic/action.py @@ -3,19 +3,22 @@ import ckanext.unfold.exception as unf_exception import ckanext.unfold.logic.schema as unf_schema -import ckanext.unfold.utils as unf_utils import ckanext.unfold.types as unf_types +import ckanext.unfold.utils as unf_utils @tk.side_effect_free @validate(unf_schema.get_archive_structure) def get_archive_structure(context, data_dict): resource = tk.get_action("resource_show")(context, {"id": data_dict["id"]}) + resource_view = tk.get_action("resource_view_show")( + context, {"id": data_dict["view_id"]} + ) try: return [ n.model_dump() if isinstance(n, unf_types.Node) else n - for n in unf_utils.get_archive_tree(resource) + for n in unf_utils.get_archive_tree(resource, resource_view) ] except unf_exception.UnfoldError as e: return {"error": str(e)} diff --git a/ckanext/unfold/logic/schema.py b/ckanext/unfold/logic/schema.py index 43e57de..865d9ed 100644 --- a/ckanext/unfold/logic/schema.py +++ b/ckanext/unfold/logic/schema.py @@ -9,9 +9,17 @@ @validator_args def get_preview_schema(ignore_empty, unicode_safe, url_validator) -> Schema: - return {"file_url": [ignore_empty, unicode_safe, url_validator]} + return { + "file_url": [ignore_empty, unicode_safe, url_validator], + "archive_pass": [ignore_empty, unicode_safe], + } @validator_args -def get_archive_structure(not_empty, unicode_safe, resource_id_exists) -> Schema: - return {"id": [not_empty, unicode_safe, resource_id_exists]} +def get_archive_structure( + not_empty, unicode_safe, resource_id_exists, resource_view_id_exists +) -> Schema: + return { + "id": [not_empty, unicode_safe, resource_id_exists], + "view_id": [not_empty, unicode_safe, resource_view_id_exists], + } diff --git a/ckanext/unfold/logic/validators.py b/ckanext/unfold/logic/validators.py new file mode 100644 index 0000000..f95a97f --- /dev/null +++ b/ckanext/unfold/logic/validators.py @@ -0,0 +1,16 @@ +from typing import Any + +import ckan.plugins.toolkit as tk +import ckan.types as types + + +def resource_view_id_exists(resource_view_id: str, context: types.Context) -> Any: + """Ensures that the resource_view with a given id exists.""" + + model = context["model"] + session = context["session"] + + if not session.query(model.ResourceView).get(resource_view_id): + raise tk.Invalid("Resource view not found.") + + return resource_view_id diff --git a/ckanext/unfold/plugin.py b/ckanext/unfold/plugin.py index 25aa677..e498879 100644 --- a/ckanext/unfold/plugin.py +++ b/ckanext/unfold/plugin.py @@ -12,6 +12,7 @@ @tk.blanket.actions +@tk.blanket.validators class UnfoldPlugin(plugins.SingletonPlugin): plugins.implements(plugins.IConfigurer) plugins.implements(plugins.IResourceView, inherit=True) diff --git a/ckanext/unfold/templates/unfold_form.html b/ckanext/unfold/templates/unfold_form.html index 6d25247..3752b18 100644 --- a/ckanext/unfold/templates/unfold_form.html +++ b/ckanext/unfold/templates/unfold_form.html @@ -1,10 +1,5 @@ {% import 'macros/form.html' as form %} -{{ form.input( - 'file_url', - id='field-file_url', - label=_('File URL'), - placeholder=_('eg. http://example.com/test.zip (if blank uses resource url)'), - value=data.file_url, - error=errors.file_url) -}} +{{ form.input('file_url', label=_('File URL'), placeholder=_('eg. http://example.com/test.zip (if blank uses resource url)'), value=data.file_url, error=errors.file_url)}} + +{{ form.input('archive_pass', label=_("Password"), id='field-password', type="password", value=data.archive_pass, error=errors.archive_pass) }} diff --git a/ckanext/unfold/templates/unfold_preview.html b/ckanext/unfold/templates/unfold_preview.html index 8fd2586..7cf21fb 100644 --- a/ckanext/unfold/templates/unfold_preview.html +++ b/ckanext/unfold/templates/unfold_preview.html @@ -16,7 +16,7 @@ -
+
{{ _("The archive tree is currently being initialized. Please wait...") }} diff --git a/ckanext/unfold/types.py b/ckanext/unfold/types.py index 371e972..0d5ba5c 100644 --- a/ckanext/unfold/types.py +++ b/ckanext/unfold/types.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Optional, Dict +from typing import Any, Dict, Optional from pydantic import BaseModel, Field diff --git a/ckanext/unfold/utils.py b/ckanext/unfold/utils.py index 3c0ad45..4e234fc 100644 --- a/ckanext/unfold/utils.py +++ b/ckanext/unfold/utils.py @@ -4,7 +4,7 @@ import logging import math import pathlib -from typing import Any, Optional +from typing import Any import ckan.lib.uploader as uploader from ckan.lib.redis import connect_to_redis @@ -124,7 +124,7 @@ def delete_archive_structure(resource_id: str) -> None: def get_archive_tree( - resource: dict[str, Any] + resource: dict[str, Any], resource_view: dict[str, Any] ) -> list[unf_types.Node] | list[dict[str, Any]]: remote = False @@ -141,16 +141,13 @@ def get_archive_tree( tree = get_archive_structure(resource["id"]) if not tree: - tree = parse_archive(resource["format"].lower(), filepath, remote) - save_archive_structure(tree, resource["id"]) + res_format = resource["format"].lower() - return tree + if res_format not in unf_adapters.ADAPTERS: + raise TypeError(f"No adapter for `{res_format}` archives") + tree = unf_adapters.ADAPTERS[res_format](filepath, resource_view, remote=remote) -def parse_archive( - fmt: str, filepath: str, remote: Optional[bool] = False -) -> list[unf_types.Node]: - if fmt not in unf_adapters.ADAPTERS: - raise TypeError(f"No adapter for `{fmt}` archives") + save_archive_structure(tree, resource["id"]) - return unf_adapters.ADAPTERS[fmt](filepath, remote=remote) + return tree