diff --git a/core/downloads/file_finder.py b/core/downloads/file_finder.py index 0e6e03a7..5ea9a5e5 100644 --- a/core/downloads/file_finder.py +++ b/core/downloads/file_finder.py @@ -77,7 +77,16 @@ def _normalize_for_finding(text: str) -> str: return "" text = unidecode(text).lower() text = re.sub(r'[._/]', ' ', text) - text = re.sub(r'[\[\(].*?[\]\)]', '', text) + # Strip ONLY balanced bracket pairs (tags like "[FLAC]", "(Remastered 2016)"). + # The old combined pattern r'[\[\(].*?[\]\)]' allowed MISMATCHED delimiters, so a + # lone unbalanced '[' — slskd reports "[34 - You & Me (Flume Remix)" but saves the + # file as "34 - You & Me (Flume Remix)" — matched from that '[' all the way to the + # next ')', eating the entire title and collapsing the search target to "flac". The + # file then scored 0.40 against the real on-disk name and was reported "not found" + # despite sitting right there. Per-delimiter pairs can't over-consume; a stray + # unbalanced bracket simply survives to the alphanumeric strip below. + text = re.sub(r'\[[^\]]*\]', '', text) + text = re.sub(r'\([^)]*\)', '', text) text = re.sub(r'[^a-z0-9\s-]', '', text) return ' '.join(text.split()).strip() diff --git a/tests/downloads/test_file_finder.py b/tests/downloads/test_file_finder.py index 5e7d6291..2bd89130 100644 --- a/tests/downloads/test_file_finder.py +++ b/tests/downloads/test_file_finder.py @@ -396,3 +396,56 @@ def test_real_soulseek_path_still_basenamed(tmp_path): str(downloads), r'shared\Billy Ocean\Very Best Of\01 - Suddenly.flac', ) assert found == str(target) + + +# --------------------------------------------------------------------------- +# Unbalanced bracket — slskd REPORTS "[34 - Title.flac" but SAVES the file as +# "34 - Title.flac" (it sanitises the leading '['). The normaliser's old combined +# bracket-strip r'[\[\(].*?[\]\)]' matched from that lone '[' all the way to the +# next ')', eating the whole title and collapsing the search target to just "flac" +# → 0.40 fuzzy score → "File not found on disk" despite the file sitting right +# there. (Discord: Shdjfgatdif — "You & Me (Flume Remix)".) +# --------------------------------------------------------------------------- + + +def test_finds_file_when_slskd_strips_a_leading_bracket(tmp_path): + downloads = tmp_path / 'downloads' + # On disk: no leading '['. API filename (slskd-reported): has the '['. + target = downloads / 'Disclosure' / '34 - You & Me (Flume Remix).flac' + _touch(target) + + found, location = find_completed_audio_file( + str(downloads), r'Music\Disclosure\[34 - You & Me (Flume Remix).flac', + ) + + assert found == str(target), \ + 'the lone "[" used to collapse the target to "flac" and miss the file' + assert location == 'downloads' + + +def test_balanced_bracket_tags_still_stripped(tmp_path): + """No regression: balanced "[FLAC]" / "(Remastered 2016)" tags in a Soulseek + filename must still be stripped so it matches the clean saved file.""" + downloads = tmp_path / 'downloads' + target = downloads / 'Song.mp3' + _touch(target) + + found, _ = find_completed_audio_file( + str(downloads), r'shared\Artist\Album\Song [FLAC] (Remastered 2016).mp3', + ) + + assert found == str(target) + + +def test_stray_closing_bracket_does_not_break_match(tmp_path): + """The other shape in the wild (Discord: "Abort, Retry, Fail_]1-01 …") — a + stray ']' must not wreck the match either.""" + downloads = tmp_path / 'downloads' + target = downloads / 'White Town' / "Fail_]1-01 Your Woman.flac" + _touch(target) + + found, _ = find_completed_audio_file( + str(downloads), r"@@digadom\Music\White Town\Fail_]1-01 Your Woman.flac", + ) + + assert found == str(target)