Commit graph

3 commits

Author SHA1 Message Date
BoulderBadgeDad
551df0c3ca downloads: fix file-finder collapsing on an unbalanced bracket (false "not found")
Discord (Shdjfgatdif): a downloaded .flac sat right there in the download folder but the import
flow reported "File not found on disk after 5 search attempts" and failed it.

root cause: slskd REPORTS the name as "[34 - You & Me (Flume Remix).flac" but SAVES it as
"34 - You & Me (Flume Remix).flac" (it strips the leading '['). The finder's fuzzy-match
normaliser used one combined bracket-strip — r'[\[\(].*?[\]\)]' — which allows MISMATCHED
delimiters, so the lone '[' matched all the way to the next ')', ate the whole title, and
collapsed the search target to just "flac". That scored 0.40 against the real filename (below the
0.85 floor) → "not found", despite the file being on disk. Confirmed by running the real code on
his exact filename.

fix: strip only BALANCED pairs (\[...\] and (...) separately). A stray unbalanced bracket now
survives to the alphanumeric strip instead of devouring the title. '[34 - You & Me (Flume Remix)'
→ matches at 1.00. Balanced tags like "[FLAC]" / "(Remastered 2016)" are still stripped (no
regression). Only used internally by the finder's fuzzy scorer — contained blast radius.

3 tests: his exact unbalanced-'[' filename, a stray-']' variant, and a balanced-tag no-regression
guard. 1311 imports/downloads/quality tests green, ruff clean.
2026-06-28 15:10:30 -07:00
BoulderBadgeDad
b36392c62b Fix: a '/' in a song title was treated as a path separator (#835)
YouTube/Tidal/Qobuz results encode the name as ``id||title``. When the title
itself contains a '/' (e.g. the Sawano AoT track "YouSeeBIGGIRL/T:T"), two places
wrongly basename-split it on the slash and kept only the last segment ("T:T"):

- core/downloads/file_finder.py — the completed-download finder truncated its
  search target to "T:T", so the real on-disk file (slash sanitised by the
  writer) never matched → "not found after processing" → the download got
  QUARANTINED. Now an encoded ``id||title`` keeps the whole title as the target
  and contributes no remote-directory components; real Soulseek PATHS still get
  basename + dir extraction unchanged.
- webui/static/downloads.js — the manual-search FILE column showed only "T:T".
  Added a ``||``-aware short-label helper (mirrors the correct handling already
  used elsewhere in the file); real file paths still show their basename.

Tests: the finder locates "YouSeeBIGGIRL∕T: T.mp3" from the encoded title
"…||YouSeeBIGGIRL/T:T" (the screenshot case), doesn't match an unrelated file,
and a genuine Soulseek path still resolves to its last segment. 21 finder tests
+ 64 script-split integrity tests pass.
2026-06-10 16:29:09 -07:00
Broque Thomas
f976a6da53 Fix: Soulseek album-bundle downloads stuck on "failed" after slskd
finished the release (#715)

Symptom (user @pavelcreates / @IamGroot60 on 2.6.2):
- Click Download on an album in the search modal
- slskd starts + completes every track of the release
- 22+ minutes after the last completed download, batch flips
  to "failed" with no clear log line explaining why
- Per-track Soulseek downloads on the same machine were fine

Root cause: ``core/soulseek_client._resolve_downloaded_album_file``
probed three hard-coded candidate paths to locate each downloaded
file in the slskd download dir:

  candidates = [
      download_path / remote_filename,
      download_path / basename,
      download_path / *normalized_path_parts,
  ]

On the common slskd config ``directories.downloads.username = true``
slskd writes files at ``<download_dir>/<username>/<filename>`` —
none of the three candidates carry a username segment, so the
resolver returned None for every file even though the file was
physically present in a subdir one level deeper. ``_poll_album
_bundle_downloads`` saw 0 completed_paths, kept spinning, and
hit the master deadline (~30 min) before bailing the batch.

Why per-track worked: ``web_server._find_completed_file_robust``
already does a recursive walk-by-basename + path-confirm against
the remote directory components, so any layout slskd writes ends
up resolved. The bundle path didn't go through it.

Fix
- Lifted the robust finder into ``core/downloads/file_finder.py``
  as a pure function ``find_completed_audio_file(download_dir,
  api_filename, transfer_dir=None) -> (path, location)``. Zero
  globals; recursive walk; handles slskd dedup suffix
  ``_<10+digit-timestamp>``, YouTube / Tidal ``id||title`` encoded
  filenames, the AcoustID-quarantine subdir skip, basename
  collisions disambiguated by remote-path components, and a
  fuzzy-basename fallback above 0.85.
- ``_resolve_downloaded_album_file`` keeps the three-candidate
  fast path (cheap probe for the slskd-flat default) but now
  delegates to the new helper when none hit, instead of giving up.
- ``_poll_album_bundle_downloads`` tracks "slskd reports
  Completed but local resolver returns None" per key. When every
  remaining key has been in that state past a 45-second grace
  window, the poll exits early with an explicit error pointing at
  the likely ``soulseek.download_path`` mismatch instead of
  silently spinning until the master deadline.
- ``web_server._find_completed_file_robust`` becomes a thin
  delegate so both callers share one finder. Legacy inline impl
  kept as ``_find_completed_file_robust_legacy`` for reference;
  to be removed next release.
- Fixed misleading ``"(0 tracks, quality=)"`` log on the preflight-
  reuse path — was reading attrs off a None ``picked`` object.

Tests (17 new in tests/downloads/test_file_finder.py)
- Flat slskd layout
- Username-prefixed (the #715 case)
- Full remote tree preserved
- Deeply nested username + tree
- File genuinely missing returns None
- Basename collision disambiguated by remote dirs
- Single basename match wins regardless of dirs
- slskd dedup suffix match
- Short ``_<digits>`` (year) not treated as dedup
- AcoustID quarantine subdir skipped
- YouTube / Tidal ``id||title`` encoded filenames
- transfer_dir fallback
- Both dirs miss → (None, None)
- Non-audio files ignored
- Empty api_filename
- Fuzzy match on punctuation variant
- Fuzzy rejects below threshold

475 downloads tests pass after the lift.
2026-05-27 21:20:37 -07:00