ckanext-unfold


Nameckanext-unfold JSON
Version 2.1.0 PyPI version JSON
download
home_pageNone
SummaryProvides previews for multiple archive formats
upload_time2025-11-05 08:38:57
maintainerNone
docs_urlNone
authorNone
requires_pythonNone
licenseAGPL
keywords ckan
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            [![Tests](https://github.com/DataShades/ckanext-unfold/actions/workflows/test.yml/badge.svg)](https://github.com/DataShades/ckanext-unfold/actions/workflows/test.yml)

# ckanext-unfold

Enhance your CKAN experience with our extension that enables seamless previews of various archive formats, ensuring easy access and efficient data management.

![Plugin presentation](https://raw.githubusercontent.com/DataShades/ckanext-unfold/master/doc/view.png)

Features:
- Represents an archive as a file tree
- Supports the following archive formats: ZIP, ZIPX, JAR, RAR, CBR, 7Z, TAR, TAR.XZ, TAR.GZ, TAR.BZ2, DEB, RPM, A, AR, LIB
- Password-protected archives support for RAR format
- Caching the file tree for faster access
- File and folder search
- Support local and remote files
- Support for large archives

## Requirements

CKAN >= 2.10

Python >= 3.11

Redis (for caching)

## Configuration

```ini
ckan.plugins = unfold
ckan.views.default_views = unfold_view
```

### Settings

See the [config declaration](./ckanext/unfold/config_declaration.yaml) file.

## Signals

The extension provides the following signals for customization and extension:
- `unfold:register_format_adapters`: Register custom adapters for specific file formats.
- `unfold:get_adapter_for_resource`: Get a custom adapter for a specific resource.

### Registering a custom adapter

You can register your own adapter for a specific file format by using the `unfold:register_format_adapters` signal.

In fact, it doesn't have to be an archive format — you can register an adapter for any file format that makes sense to be represented as a file tree. To create your own adapter, you need to inherit from `adapters.BaseAdapter` and implement the required methods.

We're providing a simple example adapter below. The node list generation is up to the developer.

```py
from ckanext.unfold.adapters import BaseAdapter
from ckanext.unfold.types import Node

class ExampleAdapter(BaseAdapter):
    def get_node_list(self) -> list[Node]:
        """Return list of nodes representing the archive structure.

        Ensure, that your implementation handles both local and remote files
        based on the `self.remote` attribute.
        """
        return self.get_mock_node_list()

    def get_mock_node_list(self) -> list[Node]:
        return [
            unf_types.Node(
                id="example_folder/",
                text="example_folder",
                icon="fa fa-folder",
                parent="#",
            ),
            unf_types.Node(
                id="example_folder/example_file.txt",
                text="example_file.txt",
                icon="fa fa-file-text",
                parent="example_folder/",
                a_attr={"href": "http://example.com/example_file.txt", "target": "_blank"},
                data={"type": "file", "size": "50 KB", "modified_at": "26/08/2021 - 20:13"},
            ),
            unf_types.Node(
                id="example_folder/example_file.pdf",
                text="example_file.pdf",
                icon="fa fa-file-pdf",
                parent="example_folder/",
                data={"type": "file", "size": "1.2 MB", "modified_at": "01/01/2024 - 00:00"},
            ),
            unf_types.Node(
                id="another_file.docx",
                text="another_file.docx",
                icon="fa fa-file-word",
                parent="#",
                data={"type": "file", "size": "1.0 MB", "modified_at": "01/01/2024 - 00:00"},
            ),
        ]
```

Then, you need to **register** your adapter using the signal. Each adapter registration function should accept a single argument, which is the adapter registry.

```py
class ExamplePlugin(p.SingletonPlugin):
    ...

    p.implements(p.ISignal)

    # ISignal
    def get_signal_subscriptions(self) -> types.SignalMapping:
        return {
            tk.signals.ckanext.signal("unfold:register_format_adapters"): [
                self._register_format_adapters
            ],
        }

    @classmethod
    def _register_format_adapters(cls, adapters: type[unf_adapters.Registry]) -> None:
        adapters.update({"my.format": ExampleAdapter})
```

Each adapter is responsible for handling a specific file format. The key in the registry dictionary is the file format, and the value is the adapter class.

> [!NOTE]
> 1. You can register multiple adapters for different file formats.
> 2. This way, you can replace existing adapters by registering your own adapter for the same format.

The result preview will look like this:

![alt text](https://raw.githubusercontent.com/DataShades/ckanext-unfold/master/doc/example_adapter.png)

## Getting a custom adapter for a resource

Sometimes, you may want to provide a custom adapter for a specific resource based on some criteria, such as resource metadata or other attributes. Or you may want not to preview certain resources. You can do this by listening to the `unfold:get_adapter_for_resource` signal and returning your custom adapter when the criteria are met.

```py
...
class ExamplePlugin(p.SingletonPlugin):
    ...

    p.implements(p.ISignal)

    # ISignal
    def get_signal_subscriptions(self) -> types.SignalMapping:
        return {
            tk.signals.ckanext.signal("unfold:get_adapter_for_resource"): [
                self._get_adapter_for_resource
            ],
        }

    @classmethod
    def _get_adapter_for_resource(cls, resource: dict[str, str]) -> type[BaseAdapter] | None | bool:
        if resource.get("format", "").lower() == "my.format":
            return ExampleAdapter

        return None
```

1. Return an adapter class if you want to provide a custom adapter for the resource.
2. If you return `None`, another extension may provide an adapter, or the default adapter lookup mechanism will be used.
3. If you return `False` from the signal handler, it will prevent further processing, and no adapter will be used for that resource.

## Dependencies

Working with different archive formats requires different tools:

### RAR, CBR

It depends on `unrar` command-line utility to do the actual decompression. Note that by default it expect it to be in `PATH`.
If unrar launching fails, you need to fix this.

Alternatively, `rarfile` can also use either [unar](https://theunarchiver.com/command-line) from [TheUnarchiver](https://theunarchiver.com/) or
[bsdtar](https://github.com/libarchive/libarchive/wiki/ManPageBsdtar1) from [libarchive](https://www.libarchive.org/) as
decompression backend. From those unar is preferred as bsdtar has very limited support for RAR archives.

It depends on [cryptography](https://pypi.org/project/cryptography/) or [PyCryptodome](https://pypi.org/project/pycryptodome/)
modules to process archives with password-protected headers.

### 7Z

We are using [`py7zr`](https://py7zr.readthedocs.io/) library.

The py7zr depends on several external libraries. You should install these libraries with py7zr.
There are `PyCryptodome`, `PyZstd`, `PyPPMd`, `bcj-cffi`, `texttable`, and `multivolumefile`.
These packages are automatically installed when installing with pip command.

For extra information, please visit the [official documentation](https://py7zr.readthedocs.io/en/latest/user_guide.html#dependencies),
especially the dependencies section.

### ZIP, ZIPX, JAR

We are using built-in library [`zipfile`](https://docs.python.org/3/library/zipfile.html). Please consider referring to the official documentation for more information.

### TAR, TAR.XZ, TAR.GZ, TAR.BZ2

We are using built-in library [`tarfile`](https://docs.python.org/3/library/tarfile.html). Please consider referring to the official documentation for more information.

### RPM

We are using [`rpmfile`](https://github.com/srossross/rpmfile) library.

If you want to use rpmfile with zstd compressed rpms, you'll need to install the [`zstandard`](https://pypi.org/project/zstandard/) module.

### DEB, A, AR, LIB

We are using [`ar`](https://github.com/vidstige/ar) library. Please consider referring to the official documentation for more information.

## License

[AGPL](https://www.gnu.org/licenses/agpl-3.0.en.html)

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "ckanext-unfold",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": "DataShades <datashades@linkdigital.com.au>",
    "keywords": "CKAN",
    "author": null,
    "author_email": "DataShades <datashades@linkdigital.com.au>, Oleksandr Cherniavskyi <mutantsan@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/cd/35/78692dd905b3283a3fde5b65a189704e3bd418bac13172b3a8e65cb40965/ckanext_unfold-2.1.0.tar.gz",
    "platform": null,
    "description": "[![Tests](https://github.com/DataShades/ckanext-unfold/actions/workflows/test.yml/badge.svg)](https://github.com/DataShades/ckanext-unfold/actions/workflows/test.yml)\n\n# ckanext-unfold\n\nEnhance your CKAN experience with our extension that enables seamless previews of various archive formats, ensuring easy access and efficient data management.\n\n![Plugin presentation](https://raw.githubusercontent.com/DataShades/ckanext-unfold/master/doc/view.png)\n\nFeatures:\n- Represents an archive as a file tree\n- Supports the following archive formats: ZIP, ZIPX, JAR, RAR, CBR, 7Z, TAR, TAR.XZ, TAR.GZ, TAR.BZ2, DEB, RPM, A, AR, LIB\n- Password-protected archives support for RAR format\n- Caching the file tree for faster access\n- File and folder search\n- Support local and remote files\n- Support for large archives\n\n## Requirements\n\nCKAN >= 2.10\n\nPython >= 3.11\n\nRedis (for caching)\n\n## Configuration\n\n```ini\nckan.plugins = unfold\nckan.views.default_views = unfold_view\n```\n\n### Settings\n\nSee the [config declaration](./ckanext/unfold/config_declaration.yaml) file.\n\n## Signals\n\nThe extension provides the following signals for customization and extension:\n- `unfold:register_format_adapters`: Register custom adapters for specific file formats.\n- `unfold:get_adapter_for_resource`: Get a custom adapter for a specific resource.\n\n### Registering a custom adapter\n\nYou can register your own adapter for a specific file format by using the `unfold:register_format_adapters` signal.\n\nIn fact, it doesn't have to be an archive format \u2014 you can register an adapter for any file format that makes sense to be represented as a file tree. To create your own adapter, you need to inherit from `adapters.BaseAdapter` and implement the required methods.\n\nWe're providing a simple example adapter below. The node list generation is up to the developer.\n\n```py\nfrom ckanext.unfold.adapters import BaseAdapter\nfrom ckanext.unfold.types import Node\n\nclass ExampleAdapter(BaseAdapter):\n    def get_node_list(self) -> list[Node]:\n        \"\"\"Return list of nodes representing the archive structure.\n\n        Ensure, that your implementation handles both local and remote files\n        based on the `self.remote` attribute.\n        \"\"\"\n        return self.get_mock_node_list()\n\n    def get_mock_node_list(self) -> list[Node]:\n        return [\n            unf_types.Node(\n                id=\"example_folder/\",\n                text=\"example_folder\",\n                icon=\"fa fa-folder\",\n                parent=\"#\",\n            ),\n            unf_types.Node(\n                id=\"example_folder/example_file.txt\",\n                text=\"example_file.txt\",\n                icon=\"fa fa-file-text\",\n                parent=\"example_folder/\",\n                a_attr={\"href\": \"http://example.com/example_file.txt\", \"target\": \"_blank\"},\n                data={\"type\": \"file\", \"size\": \"50 KB\", \"modified_at\": \"26/08/2021 - 20:13\"},\n            ),\n            unf_types.Node(\n                id=\"example_folder/example_file.pdf\",\n                text=\"example_file.pdf\",\n                icon=\"fa fa-file-pdf\",\n                parent=\"example_folder/\",\n                data={\"type\": \"file\", \"size\": \"1.2 MB\", \"modified_at\": \"01/01/2024 - 00:00\"},\n            ),\n            unf_types.Node(\n                id=\"another_file.docx\",\n                text=\"another_file.docx\",\n                icon=\"fa fa-file-word\",\n                parent=\"#\",\n                data={\"type\": \"file\", \"size\": \"1.0 MB\", \"modified_at\": \"01/01/2024 - 00:00\"},\n            ),\n        ]\n```\n\nThen, you need to **register** your adapter using the signal. Each adapter registration function should accept a single argument, which is the adapter registry.\n\n```py\nclass ExamplePlugin(p.SingletonPlugin):\n    ...\n\n    p.implements(p.ISignal)\n\n    # ISignal\n    def get_signal_subscriptions(self) -> types.SignalMapping:\n        return {\n            tk.signals.ckanext.signal(\"unfold:register_format_adapters\"): [\n                self._register_format_adapters\n            ],\n        }\n\n    @classmethod\n    def _register_format_adapters(cls, adapters: type[unf_adapters.Registry]) -> None:\n        adapters.update({\"my.format\": ExampleAdapter})\n```\n\nEach adapter is responsible for handling a specific file format. The key in the registry dictionary is the file format, and the value is the adapter class.\n\n> [!NOTE]\n> 1. You can register multiple adapters for different file formats.\n> 2. This way, you can replace existing adapters by registering your own adapter for the same format.\n\nThe result preview will look like this:\n\n![alt text](https://raw.githubusercontent.com/DataShades/ckanext-unfold/master/doc/example_adapter.png)\n\n## Getting a custom adapter for a resource\n\nSometimes, you may want to provide a custom adapter for a specific resource based on some criteria, such as resource metadata or other attributes. Or you may want not to preview certain resources. You can do this by listening to the `unfold:get_adapter_for_resource` signal and returning your custom adapter when the criteria are met.\n\n```py\n...\nclass ExamplePlugin(p.SingletonPlugin):\n    ...\n\n    p.implements(p.ISignal)\n\n    # ISignal\n    def get_signal_subscriptions(self) -> types.SignalMapping:\n        return {\n            tk.signals.ckanext.signal(\"unfold:get_adapter_for_resource\"): [\n                self._get_adapter_for_resource\n            ],\n        }\n\n    @classmethod\n    def _get_adapter_for_resource(cls, resource: dict[str, str]) -> type[BaseAdapter] | None | bool:\n        if resource.get(\"format\", \"\").lower() == \"my.format\":\n            return ExampleAdapter\n\n        return None\n```\n\n1. Return an adapter class if you want to provide a custom adapter for the resource.\n2. If you return `None`, another extension may provide an adapter, or the default adapter lookup mechanism will be used.\n3. If you return `False` from the signal handler, it will prevent further processing, and no adapter will be used for that resource.\n\n## Dependencies\n\nWorking with different archive formats requires different tools:\n\n### RAR, CBR\n\nIt depends on `unrar` command-line utility to do the actual decompression. Note that by default it expect it to be in `PATH`.\nIf unrar launching fails, you need to fix this.\n\nAlternatively, `rarfile` can also use either [unar](https://theunarchiver.com/command-line) from [TheUnarchiver](https://theunarchiver.com/) or\n[bsdtar](https://github.com/libarchive/libarchive/wiki/ManPageBsdtar1) from [libarchive](https://www.libarchive.org/) as\ndecompression backend. From those unar is preferred as bsdtar has very limited support for RAR archives.\n\nIt depends on [cryptography](https://pypi.org/project/cryptography/) or [PyCryptodome](https://pypi.org/project/pycryptodome/)\nmodules to process archives with password-protected headers.\n\n### 7Z\n\nWe are using [`py7zr`](https://py7zr.readthedocs.io/) library.\n\nThe py7zr depends on several external libraries. You should install these libraries with py7zr.\nThere are `PyCryptodome`, `PyZstd`, `PyPPMd`, `bcj-cffi`, `texttable`, and `multivolumefile`.\nThese packages are automatically installed when installing with pip command.\n\nFor extra information, please visit the [official documentation](https://py7zr.readthedocs.io/en/latest/user_guide.html#dependencies),\nespecially the dependencies section.\n\n### ZIP, ZIPX, JAR\n\nWe are using built-in library [`zipfile`](https://docs.python.org/3/library/zipfile.html). Please consider referring to the official documentation for more information.\n\n### TAR, TAR.XZ, TAR.GZ, TAR.BZ2\n\nWe are using built-in library [`tarfile`](https://docs.python.org/3/library/tarfile.html). Please consider referring to the official documentation for more information.\n\n### RPM\n\nWe are using [`rpmfile`](https://github.com/srossross/rpmfile) library.\n\nIf you want to use rpmfile with zstd compressed rpms, you'll need to install the [`zstandard`](https://pypi.org/project/zstandard/) module.\n\n### DEB, A, AR, LIB\n\nWe are using [`ar`](https://github.com/vidstige/ar) library. Please consider referring to the official documentation for more information.\n\n## License\n\n[AGPL](https://www.gnu.org/licenses/agpl-3.0.en.html)\n",
    "bugtrack_url": null,
    "license": "AGPL",
    "summary": "Provides previews for multiple archive formats",
    "version": "2.1.0",
    "project_urls": {
        "Homepage": "https://github.com/DataShades/ckanext-unfold"
    },
    "split_keywords": [
        "ckan"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "9364f82007a1fde4531b392a0a9de00710d719afb75b388d3d9490ae849ac4de",
                "md5": "562d761f2eab48f6340b05dfb5ebd48b",
                "sha256": "15a0692b0178da5489b7e0a13084056c3b0e633458affe895193cfee0649e731"
            },
            "downloads": -1,
            "filename": "ckanext_unfold-2.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "562d761f2eab48f6340b05dfb5ebd48b",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 77725,
            "upload_time": "2025-11-05T08:38:55",
            "upload_time_iso_8601": "2025-11-05T08:38:55.989838Z",
            "url": "https://files.pythonhosted.org/packages/93/64/f82007a1fde4531b392a0a9de00710d719afb75b388d3d9490ae849ac4de/ckanext_unfold-2.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "cd3578692dd905b3283a3fde5b65a189704e3bd418bac13172b3a8e65cb40965",
                "md5": "99c0add97000c832e4a25aa62ee257e0",
                "sha256": "c58265eda6e8c7019175f907ee697872ee650202f0819c342e610fd72eabc2d4"
            },
            "downloads": -1,
            "filename": "ckanext_unfold-2.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "99c0add97000c832e4a25aa62ee257e0",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 73036,
            "upload_time": "2025-11-05T08:38:57",
            "upload_time_iso_8601": "2025-11-05T08:38:57.611023Z",
            "url": "https://files.pythonhosted.org/packages/cd/35/78692dd905b3283a3fde5b65a189704e3bd418bac13172b3a8e65cb40965/ckanext_unfold-2.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-11-05 08:38:57",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "DataShades",
    "github_project": "ckanext-unfold",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": true,
    "lcname": "ckanext-unfold"
}
        
Elapsed time: 3.12321s