soulsync 3.4.5: dev → main
a login-gate security fix, a much stricter musicbrainz matcher, mp3 quality upgrades, listenbrainz and maloja listens import, a fix for the fsync stall on big libraries, a soulseek selection and slow-retry overhaul, and a pile of download, profile, audiobook and chat fixes. scope: the 61 commits since the 3.4.4 release merge (#1269).
security: login gate fails closed (GHSA-j7g5-8j44-jqhm)
- a transient sqlite read lock while checking
require_loginused to drop the login gate entirely. the last known value is now cached so a config read error keeps the gate up. - unauthenticated sessions under login mode (and unverified launch-pin sessions) are pinned to no profile, no admin, no download rights, instead of falling back to profile 1.
get_current_profile_id()returns none for them; background callers still resolve to the background profile. is_admin_request()verifies the session before looking at privileges, and a websocket gate fault now blocks the connection instead of letting it through. regression tests cover each path.
musicbrainz matching gets strict (@nstrelow, #1271 #1273 #1274 #1275)
- cross-version matches are rejected: a bare "firewater" no longer matches "firewater (acoustic)", "(live)" or "(english version)". the marker gate is symmetric and only reads a dash tail as a version qualifier when it actually is one.
- transient musicbrainz failures (timeouts, 503s, and the 200-with-"server busy" body) are no longer cached as a 30-day negative match.
raise_on_errorruns through everymatch_*path. - artist aliases finally work for recordings: the recording index only knows the printed credit, so "tatsuro yamashita" could never find a track credited 山下達郎. when the strict search misses, the artist is resolved through the alias-aware artist search and the recording search is pinned to its mbid. pins only happen on a unique exact name/alias hit, so "nirvana" and unknown names are refused rather than guessed.
- listenbrainz export waterfall: repair-flagged or mismatched mbids from the db/file rungs are skipped instead of exported, the db column is cleared when the repair job fixes the file, and a new isrc rung resolves recordings through the discovery match's deezer isrc (roughly two thirds of a 16-point coverage gain on a 2,089-track youtube mirror set).
mp3 quality upgrades replace the old copy (#1270)
- the finder could flag an mp3 below the selected bitrate, but import compared only extensions and discarded the better mp3 as redundant. finder-approved wishlist items now authorize a measured improvement under their assigned quality profile, without enabling force replacement for other wishlist items.
- original filenames are reused, mp3-only targets do not accept flac replacements, and unreadable or non-improving files cannot overwrite the library. integrity and length checks remain, and a different-format original is retired only after successful publication.
listenbrainz and maloja listens import
- the stats page can pull your listenbrainz listening history with the same run, cancel, live status in the downloads tray and completion card as the last.fm pull. run once to backfill, and hourly sync stays on from then. there is a matching "import listenbrainz listening" automation block with an optional username and a force-full-backfill switch.
- maloja works through its listenbrainz-compatible url (
/apis/listenbrainz, with the/apis/lbrnzalias) using your existing maloja api key; history is read from the native/apis/mlj_1/scrobblesendpoint on the same server. settings > services > listenbrainz gains an optional username field, resolved from the token when blank. - incremental runs scan newest-first through a 24-hour overlap with the last successful cursor and only advance it after the whole window succeeds. failed or cancelled runs retry safely, backfills keep a timestamp checkpoint, and http errors or malformed responses are reported as failures rather than empty history.
- cross-source dedup is shared with the last.fm importer and server/player history: a play is identified by normalized title, artist and exact timestamp, cross-source matches must land within 10 seconds with no conflicting album, and each stored play can represent at most one event per source. imported plays are marked as already scrobbled so outbound scrobbling does not echo them. docs in
docs/listenbrainz-history-sync.md.
soulseek downloads (@mandos21, #1262)
- search collection merges evolving slskd snapshots by peer and file instead of dropping late files or stopping at 30 responses, and exits on slskd's real terminal state (
Completed, TimedOutand friends, orisComplete) with a quiet-period fallback. trade-off: a popular track's search now runs at least 15 seconds instead of bailing at 30 responses. - candidates are ranked inside confidence bands by observed peer throughput, free slots, queue depth and advertised speed, interleaved so one uploader with many hits cannot monopolise the retry walk. slskd's current
hasFreeUploadSlotfield is honoured with the legacy numeric fallback. - opt-in "retry slow soulseek downloads" (default off, 250 KB/s floor): actual bytes over a rolling 30-second window, a second sustained low period before switching, cached alternatives before fresh searches, the slow source kept as the last resort, and a replacement only after slskd confirms the cancel. album bundles use the same aggregate logic, only InProgress transfers count, and a folder only switches when another validated folder covers the remaining tracks. peer throughput is recorded on ordinary per-track completions too, so the healthy-peer tier is reachable.
- target-aware filename interpretation (
core/downloads/soulseek_identity.py): several plausible titles per path, graded but never a rejection per-track; album coverage is a one-to-one assignment with ranked edges so one ambiguous file cannot satisfy two requests. the track-prefix regex lives incore/text/track_prefix.pyand is shared with the matching engine. replayed over 1,746 completed downloads from boulder's history: 97.0% exact, every real bundle at full coverage. - merged by hand with one addition: the parser reads a collaborator list glued to the artist with a tight dash (
01-J Balvin & Bad Bunny-MOJAITA), which scored 0 coverage for a real completed bundle. - false wishlist completion is gone: a transfer-folder hit must be the exact destination the import pipeline recorded, not a reconstructed
NN - Title.flac; unknown compilation track numbers become00and$artistis the track artist on compilations.
downloads
- batches can no longer sit in "downloading" forever (#1277): atomic album publish attempts are bounded to three, and the batch validator errors a batch whose tasks are all dispatched with no live worker for ten minutes. an age-only wishlist phantom guard was tried and pulled in the follow-up, since age alone does not prove a slow or held download is dead; the healer owns recovery from task and worker state and the wishlist keeps waiting while a batch is genuinely non-terminal.
- failed publish recovery: a batch whose publish failed is re-queued for completion checks until lifecycle succeeds or exhausts its budget, terminal batches (error, cancelled, failed) never publish again, and the staging folder of an exhausted publish is kept on disk for manual recovery instead of being swept.
- hybrid fallback on failure: when a soulseek transfer gives up (errored, queue timeout, stalled at 0%, vanished from live transfers, unknown state) the failed transfer is cancelled in slskd so no slot leaks, that source is marked exhausted, and the worker moves to the next configured source such as youtube.
- deezer as primary streaming source no longer fails with "download completed but file not found". the stream task uses the recorded file path first and recognises deezer and soundcloud usernames.
- pasted deezer track and album urls resolve directly in manual search and album match instead of running a useless text search (@mon5termatt, #1281).
- interactive downloads from search stamp the session's profile so own-library routing works from the search page too (#1279).
profiles and libraries
- an own-library profile on navidrome used to be routed silently into the shared transfer folder. it now logs a loud [own library] warning naming the profile, root and fallback, sends one throttled in-app notification, and the settings save response carries warnings when the active server is switched to one that cannot support own libraries (#1276). saving profile metadata no longer 400s for such profiles.
- navidrome and standalone libraries record per-track artist credits during scan (@sarab97, #1278).
- reorganize honours a manually set compilation record_type over the metadata provider, auto-detects multi-artist compilations (three or more distinct track artists with none over half), separates $albumartist from $artist in path templates, and persists the detected type.
- the comma-artist splitter repair job no longer loops on zero-padded id3v2.4 tags: mutagen reads the padded frame as two values, the scan flagged it and the fix called it stale forever. files that already carry the split list are skipped by the scan and resolved by the fix.
audiobooks
- the audiobook wishlist is redesigned with a dual view mode and 3d cover depth, live text filtering by title, author, narrator or series, quick-remove on the cover, clear all, and a targeted single-book search and grab.
- manual wishlist passes bypass the six-hour cooldown by default, in-flight searches register with the download batch, and interim statuses show in active downloads.
- soulseek release scoring parses durations, drops releases under 75% of the expected runtime (only when every file reports a length, so a partial sum cannot fail a good release), detects series volume conflicts, protects soundtracks and osts, supports an "importing" status, and the live detail shows peer, release title, held reason and a "books" unit.
chat
- emoji picker overhaul: continuous scroll with sticky headers, animated emojis that stay static until hovered, a live hover preview, synonym search, inline
:shortcodeautocomplete, and a discord-style hover action bar with one-click reactions. - attachments now work in plain rooms and dms via filepost, plain mode sends the bare url while soulsync keeps the interactive card and save-to-library. 50 animated noto emojis with animated reactions. dm emoji and attachment popovers open in the right panel again.
- a glowing drag-and-drop dropzone for uploads, and static asset versions come from file mtimes so a stale browser cache cannot pin old chat assets.
sync
- synced track counts show alongside the total (e.g. "1581 (1282 synced)") with a tooltip explaining folded duplicates, and deezer's album-loading overlay labels its two passes.
database
- large libraries stalled every few minutes with every endpoint timing out, including
/status: wal mode defaults tosynchronous=FULL, which fsyncs every commit (152 ms vs 0.1 ms measured on a 35k-track install), and since it is per-connection rather than stored in the file, every fresh connection paid it. connections now setsynchronous=NORMALonly when the file is actually in wal mode, and stay on FULL if the wal switch ever failed. recent commits may be lost after a power loss or os crash; the database cannot be corrupted (@jcosmao, #1267).
build and ci
- docker builds stop rebuilding the venv, apt and deno layers on every commit: the commit sha arg moved below them so the buildx cache can hit on both architectures (@splitsec2, #1282).
- ci regressions fixed: the navidrome own-library test restores the active server after itself,
core/imports/paths.pytolerates config stubs withoutget_active_media_server, the version-guard fake acceptsraise_on_error, the sync progress handler checksframe.playlist_idagain, and autoformattedindex.htmlstrings were unwrapped so the markup tests match. best-effortexcept: passsites carrynoqa: S110. - #1272 and #1274 were merged by hand against dev's later
match_recordingchanges (the strict search keepsraise_on_error, the version-marker gate moved into the shared scorer so the pinned retry gets it too, plus a test for that); the merged suites (355 matching/export tests) pass.