Skip to content

Add Slskd support: Both for indexer and download client#465

Open
T4g1 wants to merge 4 commits intoListenarrs:canaryfrom
T4g1:feature/183-add-slskd-support
Open

Add Slskd support: Both for indexer and download client#465
T4g1 wants to merge 4 commits intoListenarrs:canaryfrom
T4g1:feature/183-add-slskd-support

Conversation

@T4g1
Copy link
Copy Markdown
Contributor

@T4g1 T4g1 commented Apr 1, 2026

Summary

Fix #183: Adds Slskd (https://github.com/slskd/slskd) support both for:

  • Indexer: As a search provider
  • Download client

I also tried to streamline the process of managing those with the objective of making it easier to add indexers and/or download clients

This PR is way too big, sorry about that :x

Added

  • Proper download client selection based on IDownloadClientAdapter and supported protocols to fetch list of DownloadClientConfiguration types to look for
  • Slskd indexer support using the native API
  • Slskd download client support using the native API
  • Basic mock class and basic test class to reduce setups required to run those

Changed

  • Indexer model updated to support enumeration and enhanced attribute name
  • Improved Protocol/Implementation attributes by adding enumeration: Less typo/error prone than having string constant all over the place
  • Factorizing some methods into specific classes where they belonged to reduce backend complexity and improve separation of concern
  • Improved search provider and download client adapter handling to make it easier to add or update them

Test coverage

  • Tests Add, Remove and Fetch from Slskd adapter
  • Test basic Search from Slskd search provider

Risks

  • Model changed through migration: Having access to few data localy, it may be required to double check the migration code to make sure it works for every cases
  • Backend for search/download and polling is modified to support third-party that associates one download with multiple transfers (previous data structure assumed one download was at most one transfer)

Next steps

  • It may be useful to make front end for the indexers and download clients form rely more on configuration rather than complex logic that may be inadvertly broken when adding new ones
  • Properly supports download client selection based on priority settings: Right now, only the first one is considered, each of them should be tried until one succeeds by order of priority
  • DownloadConfigurationClient.Type should probably be an enumeration or at least restricted by the DownloadClientAdapter configured
  • There is a lot of code duplication in classes that make use of IDownloadOrchestrator that could be removed
  • IDownloadOrchestrator and other interfaces in general should not expose or expect models specifity like technical object ID but rather pass objects except for API interfaces
  • Direct download should be implemented by an adapter and not hardcoded as a special case
  • TorrentHash and ClientDownloadId should be reconciliated, I believe removing the TorrentHash logic to make the ClientDownloadId generic instead is the way to go (it simplifies the downloads processing imho)
  • It could make sense to create a base class for interfaces (defining user/browser agents, http methods helpers, ...) to use for Adapters and Providers

@T4g1 T4g1 marked this pull request as draft April 1, 2026 19:22
@T4g1 T4g1 force-pushed the feature/183-add-slskd-support branch from 2daa9d8 to 7bff277 Compare April 2, 2026 15:05
@T4g1 T4g1 force-pushed the feature/183-add-slskd-support branch 4 times, most recently from c9b2926 to 2192bd6 Compare April 7, 2026 12:28
@T4g1 T4g1 marked this pull request as ready for review April 7, 2026 12:28
Copy link
Copy Markdown
Collaborator

@therobbiedavis therobbiedavis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! There's some small logic issues we need to handle and I haven't done a real-world test of the implementation yet, but overall it looks good so far.

Comment thread fe/src/components/domain/download/DownloadClientFormModal.vue Outdated
Comment thread listenarr.api/Services/Adapters/SlskdAdapter.cs
Comment thread listenarr.api/Services/Adapters/SlskdAdapter.cs
if (transfer.Status == "completed")
{
// Download completed
await service.FinalizeDownloadAsync(download, download.DownloadPath, client, ct);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3/3 fire-and-forget awaits.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@T4g1 i just noticed here that downloadpath may not actually be where slskd stores the files.. User could be using docker. We need to get the path from Slskd (MaybeOutputPath from GetItemsAsync and compare and translate it to the users remote mapping directory.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, i'll investigate that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, after investigation (I figured that I completely forgot to actually test that part), I added several things:

  • Configuration of Download path is now correctly used (simply ignored before)
  • If not present, API call to get the configured one on Slskd side (so the tooltip in the frontend stays valid eh eh)
  • Added actual test for that part
  • Also added a block long comment explaining how Slskd handles the file/directory thing and collisions, I might do a PR on Slskd side to update the API and simplify things in that regards if i remember that issue long enough (might be worth an issue somewhere)

Lastly, I did not use path mapping in any way here, I assumed that if Slskd is dockerized, the configured path will be a docker specific one and hence not accessible by Listenarr but in that case, user can still override it in Download Client configuration and input a correct one as visible by Listenarr. UI/documentation about that behavior might need to be updated though

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lastly, I did not use path mapping in any way here, I assumed that if Slskd is dockerized, the configured path will be a docker specific one and hence not accessible by Listenarr but in that case, user can still override it in Download Client configuration and input a correct one as visible by Listenarr.

Yes, so that remote path mapping option for the download clients is exactly the use-case you described here. It would take the path from slskd, compare against the remote path mapping, and translate it to the destination path the the user has setup.

For example, my listenarr and qbit run both in docker with different paths. Listenarr can't access /downloads/listenarr because that doesn't exist in the listenarr docker container, only in the qbit docker container, but /server/downloads/complete/listenarr/ does exist in listenarr. Both paths map though to the same destination on the file system, it's just how they're mounted in the docker config.

image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After investigation and according to the codebase, as long as QueueItem content path is filled, path mapping is already applied (I do not understand why some specific adapters are still calling that service manually, imho, Adapters should not care about this), I'll add some test to make sure it's the case though

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test added, indeed, the QueueItem contentPath is already processed if the job contains it as sourcePath.

I removed the custom normalization from remote path mapping too to normalize normalization methods

Comment thread listenarr.api/Services/DownloadMonitorService.cs Outdated
Comment thread fe/src/components/domain/search/ManualSearchModal.vue
Comment thread fe/src/components/settings/IndexerFormModal.vue
Comment thread listenarr.api/Services/Search/Providers/SlskdSearchProvider.cs Outdated
Comment thread listenarr.api/Services/Search/Providers/SlskdSearchProvider.cs Outdated
@T4g1 T4g1 requested a review from therobbiedavis April 14, 2026 12:20
@T4g1
Copy link
Copy Markdown
Contributor Author

T4g1 commented Apr 14, 2026

@therobbiedavis I think this is ready for the second round of review 💯

Comment thread listenarr.domain/Models/Indexer.cs Outdated
Comment thread fe/src/components/domain/search/ManualSearchModal.vue
if (transfer.Status == "completed")
{
// Download completed
await service.FinalizeDownloadAsync(download, download.DownloadPath, client, ct);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@T4g1 i just noticed here that downloadpath may not actually be where slskd stores the files.. User could be using docker. We need to get the path from Slskd (MaybeOutputPath from GetItemsAsync and compare and translate it to the users remote mapping directory.

Comment thread listenarr.api/Services/Adapters/SlskdAdapter.cs
Comment thread fe/src/components/domain/search/ManualSearchModal.vue
Comment thread listenarr.api/Services/Search/Providers/SlskdSearchProvider.cs Outdated
Copy link
Copy Markdown
Collaborator

@therobbiedavis therobbiedavis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a couple more things that I have questions about

Comment thread listenarr.infrastructure/Migrations/20260403104655_UpdateDownloadProtocol.cs Outdated
Comment thread listenarr.api/Services/Adapters/SlskdAdapter.cs Outdated
@T4g1 T4g1 force-pushed the feature/183-add-slskd-support branch from 4f2c1a7 to 363d5a2 Compare April 15, 2026 17:21
@T4g1 T4g1 requested a review from therobbiedavis April 15, 2026 17:23
@T4g1
Copy link
Copy Markdown
Contributor Author

T4g1 commented Apr 15, 2026

@therobbiedavis I believe I've processed all your comments, let me know if I missed anything

Copy link
Copy Markdown
Collaborator

@therobbiedavis therobbiedavis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need some branching logic for slskd otherwise I don't think automatic downloads will work.

Comment thread listenarr.api/Controllers/LibraryController.cs Outdated
Comment thread listenarr.api/Services/AutomaticSearchService.cs
{
Name = string.IsNullOrEmpty(nameFromPayload) ? (string.IsNullOrEmpty(baseUrlFromPayload) ? "Prowlarr Indexer" : baseUrlFromPayload) : nameFromPayload,
Implementation = string.IsNullOrEmpty(implementationFromPayload) ? "Custom" : implementationFromPayload,
Implementation = Implementation.Custom, // TODO: Prowlarr use Cardigann
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will break prowlarr imported indexers. Prowlarr sends over Torznab or Newznab for the implementation in the payload.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be fixed but im not sure about the schema sent from Prowlarr so I based the logic on what was already there, feel free to check

Comment thread listenarr.api/Services/AutomaticSearchService.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Requesting support for soulseek/slskd

2 participants