feat(file-management): add symbolic link import action - #771
Conversation
Add a configurable symbolic link file action for virtual and FUSE-backed storage workflows. Preserve existing symlink targets during import, avoid copying file contents, and keep source files under downloader control. Expose the new action in the frontend and add coverage for regular files, absolute links, relative links, and failed imports. Supports use cases discussed in Listenarr Discussion Listenarrs#334, including NZBDav, rclone, FUSE, and Real-Debrid mounted storage. Parts of this implementation were developed with the assistance of AI. All generated changes were reviewed, adjusted, and tested before committing.
Build the custom Listenarr image with GitHub Actions and publish it to GitHub Container Registry for linux/amd64.
|
Built
The cross-mount symlink row is the one that matters, since that is the case a hardlink cannot serve and the reason this action exists. The bottom two rows are a regression check: the existing action behaves on your branch exactly as it does on canary. One thing worth a line in the docs rather than a code change. The link records the path as the importing process sees it, so in a container it points at the container's path for the source: Inside the container that resolves and It caught me out, for what it's worth. My first run judged the link with On merge order rather than the code: three of the thirteen files here also change on #717, which is You can re-run any of this yourself: git clone https://github.com/m4bard/listenarr-testdata && cd listenarr-testdata
python3 -m venv .venv && .venv/bin/pip install -e .
git clone --depth 1 --branch feature/symlink-import \
https://github.com/sebbeke/Listenarr.git /tmp/pr771
podman build --network=host -t listenarr-vet:pr771 /tmp/pr771
./tools/validate_import_action.sh localhost/listenarr-vet:pr771 --action symlink
./tools/validate_import_action.sh localhost/listenarr-vet:pr771 --action hardlink/copyIt generates a library, drives the import through the API, and exits non-zero if the destination is not what the action promised or the source went missing. |
|
#717 adds strict symbolic-link/alias safety, pinned/no-follow publication primitives, durable directory ownership, and required native link-capability tests on Linux and Windows. The symbolic-link import action remains a useful distinct feature, but its current direct Directory.CreateDirectory + File.CreateSymbolicLink + overwrite publication path must be reworked on top of those contracts. Please rebase on #717 and route destination creation/publication through the ownership/mutation primitives and shared native capability attributes. |
Summary
Adds a configurable Symbolic Link completed-file action for virtual, remote, and FUSE-backed storage workflows.
This allows Listenarr to import audiobooks without copying their contents locally when the source is exposed through systems such as NZBDav, rclone, Zurg, or other mounted storage providers.
This addresses the use case discussed in Discussion #334, where hardlinks are not possible because the source and destination are located on different filesystems.
Changes
Added
Symbolic Linkfile action to the backend.Changed
Motivation
The existing
Hardlink/Copyaction is not suitable for workflows where completed downloads are exposed through a separate filesystem, such as an rclone or WebDAV FUSE mount.In those environments:
The new action creates a symbolic link instead, avoiding the full copy while still allowing applications such as Audiobookshelf to access the audiobook through the mounted source.
Example:
Supported workflows
This implementation is intended to support workflows including:
The symbolic-link target must remain available for the imported audiobook to remain accessible. Applications consuming the library must also have access to the target under the same absolute path.
Testing
The implementation was tested for:
Notes
Related discussion: #334
Parts of this implementation were developed with AI assistance. All AI-assisted changes were reviewed and adjusted before committing, and the resulting implementation was tested against the scenarios described above.