Skip to content

Feature request: Preserve Symlink completed-file action for virtual filesystem download clients #814

Description

@sawdoctor
## Summary

I'd like to propose a new completed-file action called **Preserve Symlink**.

The use case is download clients / virtual filesystems such as nzbDAV / InfiniDysk, where a completed audiobook may already be exposed as a filesystem symlink to virtual storage rather than as a normal local file.

Listenarr's existing Move / Copy / Hardlink-Copy actions are not safe for this workflow because copying or falling back to a copy can materialize the entire virtual file onto local storage.

## Proposed behaviour

When **Preserve Symlink** is selected:

- The completed source must be a symbolic link.
- The symlink target must be absolute.
- Listenarr recreates the same symlink at the organised library destination.
- A regular source file is rejected.
- A broken source symlink is rejected.
- An existing different destination is not overwritten.
- There is **no File.Copy, stream-copy, hardlink or move fallback**.
- Destination planning should avoid content-equivalence checks that would dereference/read the virtual audiobook payload.
- On success the destination is returned/registered normally by Listenarr.

The important safety property is that selecting Preserve Symlink can never silently turn into a full file copy.

## Why I think this should be generic

Although I developed and tested it with nzbDAV / InfiniDysk, the implementation does not contain any InfiniDysk-specific paths or APIs.

It should therefore also be useful for other download clients or storage systems that expose completed files as symlinks.

Example:

```text
Download client

/mnt/nzbdav/completed-symlinks/audio/book.m4b
    ↓ symlink to
/mnt/nzbdav/.ids/<id>

Listenarr Preserve Symlink

/audiobooks/Author/Book/book.m4b
    ↓ same symlink target
/mnt/nzbdav/.ids/<id>

Current implementation / testing

I have a working implementation in my fork:

https://github.com/sawdoctor/Listenarr/tree/infinidysk-compat

The feature has been tested with:

  • real nzbDAV / InfiniDysk audiobook imports
  • multi-file audiobook imports
  • Audiobookshelf reading the resulting library symlinks
  • seeking/playback through the virtual files
  • source-symlink → destination-symlink unit testing
  • regular-file rejection testing
  • frontend support for selecting preserve-symlink

Targeted tests currently pass:

  • frontend: 10 passed
  • Preserve Symlink backend tests: 2 passed, 0 failed

The implementation deliberately fails closed if the completed source is not a symlink.

Upstream PR

If this is something you would accept upstream, I'm happy to prepare a clean PR rebased onto the latest canary.

My current fork branch also contains two unrelated fixes found during testing, so I would split those out and submit Preserve Symlink only in its own PR in accordance with the contribution guidelines.


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions