Repository navigation
Conversation
from it through a slskd instance, added as a self-contained source plugin with a full test suite. Soulseek is different from the torrent and usenet sources Shelfmark already supports: a search result is a file sitting on a specific peer, and only the slskd daemon that found it can fetch it, so search and download ship together as one plugin package under shelfmark/release_sources/slskd/ rather than as a new download-client protocol. The API layer wraps just the slice of slskd's REST interface Shelfmark needs and deals with its quirks (asynchronous searches that need polling and cleanup, transfer records that must be cancelled before they can be removed, older versions that return an empty body on enqueue). The source turns peer responses into releases that fit the existing release list, and the handler drives a download through slskd's transfer queue and then hands the finished files to Shelfmark's normal post-processing pipeline, so naming templates, library delivery and multi-user requests all work unchanged. Configuration is a new settings tab, and the completed-files location can be given either as a direct path or through the existing Remote Path Mappings mechanism. The main pieces: - API client — SlskdClient with API-key auth, connection test (verifies the key and that slskd is logged in to Soulseek), search create/poll/collect/delete, transfer enqueue/list/get/cancel, and lookup of slskd's downloads directory. - Release source (indexer) — runs title+author then bare-title queries; ebooks become one release per file, audiobooks group a peer's folder of audio files into one multi-file release (archives kept separate); locked files are skipped, results are filtered by your Supported Formats and ranked free slot → shortest queue → fastest upload; custom columns show peer, slot availability, format and size. - Download handler — enqueues every file of a release, polls progress with speed and remote queue position, fails fast on peer errors, honours cancel and a configurable queue timeout, locates completed files at slskd's <downloads>/<last remote folder>/<file> layout (via the Downloads Path setting or a path mapping for the new slskd client), stages multi-file releases into TMP_DIR so stranger files in a shared folder are never imported, and removes the transfer record and empty folder after a successful import; the download spec is persisted on the task so retries survive a restart. - Settings — "Soulseek (slskd)" tab: enable, URL, API key, Test Connection, downloads path, search wait, max peer responses, queue timeout, remove-completed toggle. - Supporting changes — ReleaseProtocol.SOULSEEK, source registration, slskd in the path-mapping client dropdown, frontend protocol type and dot colour, readme and environment-variable docs. - Test tooling — an slskd service in docker-compose.test-clients.yml and scripts/test_clients.py --write-slskd-config to bootstrap a config with a throwaway Soulseek login and API key. - Tests — 148 new tests covering the API client, release building, the full handler lifecycle against a scripted fake slskd (success, multi-file staging, peer failure, cancel, queue timeout, missing and unsafe paths, cleanup), the settings tab, and the queue → task → retry round-trip through the orchestrator, plus live tests that run against a real instance when SLSKD_TEST_URL/SLSKD_TEST_API_KEY are set. Everything was verified end to end against a running slskd 0.26.0 logged in to Soulseek — a real search returned 37 releases, one was downloaded and its size confirmed on disk, and cancelling a 15-file audiobook mid-transfer left no records behind — and the full suite of 3172 tests, ruff, basedpyright, vulture and the frontend typecheck all pass.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The repo's formatter wants the unparenthesised exception list that Python 3.14 allows, which is what failed the Python Quality job on the original PR.
…, events The client gains the three calls an isolated download needs. enqueue_batch queues files from one peer into a destination relative to slskd's downloads folder and returns the transfers it created and the files it refused; a slskd that predates batches (404/405) is reported as SlskdBatchUnsupportedError so the caller can fall back. get_batch reads a batch with all its transfers, removed ones included. get_events reads slskd's stored events, newest first; they survive anything clearing the transfer list.
…d's list Several apps can share one slskd and any of them can clear its list of finished transfers. The handler polled that list, so a book that finished while another app cleared it was reported as 'Transfer disappeared from slskd' though the file had arrived. It also let slskd lay files out in its shared download folder by the peer's folder name, and moved them out and deleted the record afterwards, which ended the sharing. A new isolated mode (on by default, SLSKD_ISOLATE_DOWNLOADS) fixes that: - each download is a batch into its own folder, <prefix>/<task id> (SLSKD_DESTINATION_PREFIX, default 'shelfmark'), so nothing else shares it; - every transfer is read by its id, never through the list; - the files on disk decide completion (sizes are compared, not names); slskd's stored events confirm a vanished transfer whose file is still arriving; - files already on disk from an earlier run are not downloaded again, and files slskd says are already queued are found by name and joined; - while a peer queues the files the orchestrator is asked for a stall grace, renewed and released, as the torrent handler does; - the folder is handed over as the client's own (original_download_path), so the orchestrator copies or hardlinks instead of moving, and the finished files stay in slskd (still shared) unless SLSKD_KEEP_COMPLETED is turned off. A slskd without the batch endpoint, or SLSKD_ISOLATE_DOWNLOADS off, uses the original path unchanged.
… not replaced by the saved value Adds Give each download its own folder, Download Folder and Keep finished files in slskd to the Soulseek tab, and scopes the older Remove completed option to the non-isolated path. Test Connection now treats a key the form sent as empty as empty, instead of falling back to the saved value (a review point on the original PR).
…folder text to auto-download - An m4b/m4a next to loose chapters is a different copy of the book, so it is its own release (as archives already are). - Results of several query variants are merged per peer before releases are built, so a folder's chapters found by different queries are one release instead of the first partial folder winning (a review point on the original PR). - Releases carry the remote folder path as extra['author'] and the folder name as extra['title_raw']: a Soulseek result has no author field, and these are what auto-download's author and bundle checks read. - The source is unavailable without an API key as well as without a URL.
…es are being located A review point on the original PR: _poll had already returned, so the cancel path only reported 'Cancelled' and the finished transfers stayed in slskd's list.
…do not group bare filenames into one audiobook
Contributor
Author
|
I pushed one more commit to this branch. Copilot's two reviews on #1335 raised several points, and I went back through them against this code. Most were already handled here: the cleared form field in the settings test, Two were still open, so this commit covers them:
Both have tests that fail without the change. I left the search timeout alone. Copilot says slskd reads |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add Soulseek (slskd) as a release source, building on #1335
This is @dskvr's work from #1335 rebased onto current
main, with my changes on top. #1335 has been quiet since 2026-09-13 and I needed the changes below, so I carried it forward instead of starting over. Their two commits are the first two in this branch with their authorship intact, and the source plugin, handler, API client, settings tab, tests,ReleaseProtocol.SOULSEEK, test client compose file and frontend bits are all theirs. I haven't changed how a search turns into releases beyond what is listed here.If @dskvr would rather keep #1335 alive and take these as commits on their branch, I can do that instead and close this.
What I changed
Downloads survive other apps using the same slskd. The handler found its files by listing slskd's downloads, and another app on the same instance (SoulSync here) sends
DELETE /transfers/downloads/all/completedafter each of its batches. Once that ran, a finished download no longer appeared in the list and the task never saw it complete, even though the files were on disk. With the newSLSKD_ISOLATE_DOWNLOADSsetting (on by default) each task:POST /transfers/downloads/batches) into its own folder,<SLSKD_DESTINATION_PREFIX>/<task id>/eventslog as a fallback when the transfers are gone and the files aren't visible yetSLSKD_KEEP_COMPLETEDis off and the import workedWith isolation off the handler behaves as it did in #1335. If the batch endpoint isn't there (404/405) it falls back to the original per-file enqueue.
Source changes. m4b/m4a files are their own release next to loose chapter files, so auto-download can prefer one file over a chapter set. Results from the query variants are merged per peer. The folder text is exposed as
extra["author"]andextra["title_raw"], which is what auto-download's strict match and pack detection read.is_available()also needs an API key now.Smaller fixes. Cancelling while files are still being located now removes the transfer records it created. In the settings tab, a cleared form field is no longer replaced by the saved value when testing the connection. The new settings are in
docs/environment-variables.md.Testing
tests/slskdhas 192 passing and 6 live tests that skip without an instance. I wrote the new tests first and checked the handler and source rules by breaking them and confirming a test failed. Ruff is clean. On the whole suite the only failures are 4 intests/downloadthat importseleniumbase, which isn't installed in my environment, and they fail the same onmain.I've been running this on my own deployment against slskd 0.26.0 behind a VPN, with SoulSync active on a second instance. A search returned a couple hundred results from about 70 peers, an epub came down and was imported, and auto-download fetched five audiobooks as single m4b files. That is a day of use, so I don't know how it behaves on other slskd versions or on Windows path mappings.