soulsync 3.2.3: dev → main
the sync page became a real playlist library, downloads narrate what they're actually doing, and a pile of reported bugs got their root cause rather than a patch.
the sync page is now a playlist library
it used to be a list you scrolled. now you can search it, filter by scheduled or unscheduled, and the three tabs have names that say what's in them.
every playlist card does its own work. schedule one from its own card without opening a modal, pick quality, sync now, all from the popover. the popovers stay on screen instead of running off the bottom, they toggle shut when you click the trigger again, and they work on touch.
the cards also tell the truth about ownership now. the page used to guess how much of a playlist you owned; the matcher records it instead, so the number comes from the same place the sync does. a playlist you discovered but don't own says so. cards that aren't scheduled stopped shouting for attention.
auto-sync got folded into one Activity surface rather than three places saying slightly different things, and the timezone line folds away when you don't need it.
mobile got a real pass. the filter chips scroll, the page stopped being 793px wide on a 390px screen, the action buttons stopped landing on top of the playlist name, and the whole thing gave back about a third of the screen it was wasting.
downloads say what they're doing (#1156)
"Loading..." is gone. the cards, the modal and album releases all narrate the live search and download: what it's searching, what it found, what it's pulling. same for pasting a deezer link, which now tells you where it's up to instead of sitting there.
youtube premium quality, thanks @lucasssvaz (#1154)
searches the youtube music catalog with cookies.txt auth, fetches premium itags through web_music, and ranks original opus/aac against your quality profile instead of guessing. mixed pools get scored per source, so a youtube result and a soulseek result are compared on the same terms. opus bitrate is estimated and VBR averages are marked in enhanced view rather than reading as a hard number.
the expired download cleaner keeps music people care about
it could delete files someone had favourited. it reads favourites, ratings and playlists off plex and jellyfin now and spares anything anyone marked.
the safety story got tightened twice more. every db call feeding that check used to fail open, so a brief plex outage looked exactly like "nobody likes any of this", which is precisely when it starts deleting. the sweep records whether every expected server and user really stored, and the cleaner won't delete without a complete fresh snapshot. thanks @nick2000713 for that one.
soulseek stopped ignoring your concurrency limit (#1166)
max_concurrent was enforced per batch and so was the lock guarding it, so three batches each happily ran their "one" download at once. the cap is global now. a batch that would exceed it holds and says so in the log, and whoever frees a slot nudges the batches that are waiting, so a cap can't turn into a stall. reported with the log lines that made it findable.
the request endpoint answers correctly (#1168)
POST /api/v1/request returned 500 on every success, because api_success() already returns a tuple and the caller wrapped it in another one. it answers 202 now.
completed was also never reachable, and nothing was ever going to set it. transfers get watched to a terminal state from one shared sweep, one bulk read settling every in-flight request at once, so fifty requests cost one call instead of fifty threads. on timeout the status stays downloading and gains timed_out rather than claiming a failure nobody verified.
approving a quarantined file makes it stay approved
reported by TheHomeGuy, who'd approved the same songs about five times.
the gate that stops the picker re-choosing an upload that already failed verification had no storage of its own. it was built by reading the sidecar files in the quarantine folder, so the review queue WAS the blocklist, and approve, delete and clear all delete sidecars. tidying the list erased the record of what had failed, and since ranking is deterministic the same broken file won again.
that verdict lives in the database now and nothing in the review queue removes it. the count at the top updates on its own instead of only when you click into the tab, and the dashboard has a Review button that shows how many files are waiting.
a rolled-back album takes the library pointer with it
atomic album publishing is all or nothing now, so a failure halfway through doesn't leave an album half in the library and half in staging with the batch marked complete. thanks @nick2000713.
the rollback needed finishing: it moved the files back but not the database rows, so a three track album whose third move failed put the first two back in staging with the library still insisting they were live at paths that no longer existed. it repoints on the way back now, and only for files that actually moved.
quality profiles save what you sent (#1103)
POST /api/quality-profile wrote six of fourteen columns, so eight settings could never change through the api. writing all fourteen instead turns out to be worse: the settings page only posts six, so every key you didn't send got coerced to a default and written, and the ladder editor autosaves. reordering your formats was enough to silently switch off lossy copy, acoustid strictness, deep verify and replace lower quality.
presence decides now. name a key and it saves, including turning it off; leave it out and it's left alone. and editing one of those settings while previewing a different profile finally tells you it isn't going to stick. thanks @nick2000713.
wishlist and matching, thanks @nstrelow
wing it mode can optionally search unverified guesses (#1147) instead of blanket-skipping the wishlist gate, stub metadata gets normalized before the placeholder check, and a stale flag stopped being mistaken for an unresolved guess (#1160).
matching got a version-stripped last resort on the download query ladder (#1163), musicbrainz artist aliases are resolved before giving up on a track (#1161), and dual-title romanization is canonicalized before matching rather than only after (#1162).
reported bugs
- #1155 collapsing the sidebar now actually reclaims the space, and the sidebar is collapsible in the first place
- #1159 manual matches stick, jellyfin remove/replace works, thumbs load, and punctuation glued to a word stopped making a track unfindable
- #1164 modal thumbs go through the proxy, unmatched rows stop borrowing art, and title-only matches stop reporting 100%
- #1150 the soulseek filter honours per-item quality profiles
- #1127 navidrome paths resolve when the filename itself is synthesized
- file_path corruption during library scans, thanks @shkarlsson (#1158)
- album and EP search results show their title again instead of just a picture
- a staged album stops being throttled to one worker once it's staged
- giving up on a task cancels it in slskd too, and the album stall-guard does the same
- post-processing stopped queueing behind soulseek searches
- the download subsystem was logging nowhere app.log could see
- Concurrent Downloads became a real setting instead of a number nothing read
- account playlists survive leaving the page, reported by Specialmed
- a hovered worker orb rises above the orbs its tooltip covers
- deezer got one shared rate budget, english genres, real sync status and live progress
- atomic album staging failed hard on every docker install
under the hood
- MetaSync export endpoint, a read-only keyset walk of the library's resolved metadata for the peer-to-peer sidecar. incremental export used to return nothing at all, because
sincewas compared as text and sqlite writes a space where ISO writes a T. it compares instants now, and the rows whose payload changes without their own timestamp moving get picked up too. thanks @nick2000713 soul_id_pathwas an additive column nothing ever filled, so every existing install had it empty. there's a migration that proves each path by recomputing it rather than regenerating ids peers have already traded claims on- a rate limit could overwrite a manual match, because a failed fetch returned the same "nothing stored" answer as an empty column. three states now, and an id is only released by an explicit re-match