Downloads: version-mismatch fallback also fires when retry search finds no candidates

The existing fallback (pipeline.py:1084) only ran inside
post_process_matched_download_with_verification — i.e. when a file *was*
downloaded and AcoustID retries were fully exhausted.  If the retry *search*
itself found zero valid candidates (source returned nothing, or all failed
HiFi validation), the task was marked not_found and the fallback was never
reached, even though the quarantine already held N version-mismatch entries.

Fix: add try_version_mismatch_fallback to TaskWorkerDeps; in the "no valid
candidates" path of task_worker, invoke it before marking not_found when
is_quarantine_retry.  Wired in _build_task_worker_deps via a new helper
(_try_version_mismatch_fallback_for_worker) that calls
try_accept_version_mismatch_fallback directly with the track's title and
artist and a reprocess lambda over _post_process_matched_download_with_verification.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dev 2026-06-05 23:38:41 +02:00
parent 69a37e96b7
commit 9b1445b3b5
2 changed files with 58 additions and 0 deletions

View file

@ -151,6 +151,7 @@ class TaskWorkerDeps:
attempt_download_with_candidates: Callable # (task_id, candidates, track, batch_id) -> bool
on_download_completed: Callable # (batch_id, task_id, success) -> None
recover_worker_slot: Callable # (batch_id, task_id) -> None
try_version_mismatch_fallback: Optional[Callable] = None # (title, artist, task_id, batch_id) -> bool
def download_track_worker(task_id: str, batch_id: Optional[str], deps: TaskWorkerDeps) -> None:
@ -589,6 +590,15 @@ def download_track_worker(task_id: str, batch_id: Optional[str], deps: TaskWorke
# If we get here, all search queries and hybrid fallbacks failed
logger.warning(f"[Modal Worker] No valid candidates found for '{track.name}' after trying all {len(search_queries)} queries.")
# Last-resort: quarantine retry with no new candidates — the retry search
# exhausted all sources. If the setting is enabled, accept the best
# already-quarantined candidate rather than leaving the track missing.
if is_quarantine_retry and deps.try_version_mismatch_fallback:
_fallback_artist = track.artists[0] if track.artists else ''
if deps.try_version_mismatch_fallback(track.name, _fallback_artist, task_id, batch_id):
return # fallback re-dispatched; batch completion handled by reprocess thread
with tasks_lock:
if task_id in download_tasks:
download_tasks[task_id]['status'] = 'not_found'

View file

@ -16896,6 +16896,53 @@ def _run_post_processing_worker(task_id, batch_id):
from core.downloads import task_worker as _downloads_task_worker
def _try_version_mismatch_fallback_for_worker(expected_title, expected_artist, task_id, batch_id):
"""Called by the download worker when a quarantine-retry search finds no
candidates. Delegates to version_mismatch_fallback so the best already-
quarantined version-mismatch candidate is accepted rather than giving up."""
import threading
import time
from core.imports.version_mismatch_fallback import try_accept_version_mismatch_fallback
from core.imports.quarantine import approve_quarantine_entry, list_quarantine_entries
from config.settings import config_manager
from core.imports.paths import docker_resolve_path
import os
try:
download_path = docker_resolve_path(
config_manager.get('soulseek.download_path', './downloads')
)
quarantine_dir = os.path.join(download_path, 'ss_quarantine')
restore_dir = os.path.join(download_path, 'Transfer')
def _reprocess(restored_path, ctx, tid, bid):
new_key = f"vmfallback_{tid}_{int(time.time())}"
threading.Thread(
target=lambda: _post_process_matched_download_with_verification(
new_key, ctx, restored_path, tid, bid
),
daemon=True,
).start()
return try_accept_version_mismatch_fallback(
quarantine_dir=quarantine_dir,
restore_dir=restore_dir,
expected_title=expected_title,
expected_artist=expected_artist,
task_id=task_id,
batch_id=batch_id,
config_get=config_manager.get,
list_entries=list_quarantine_entries,
approve_entry=approve_quarantine_entry,
reprocess=_reprocess,
)
except Exception as exc:
import logging
logging.getLogger(__name__).debug(
"[Version-Mismatch Fallback] worker-path skipped due to error: %s", exc
)
return False
def _build_task_worker_deps():
"""Build TaskWorkerDeps bundle from web_server.py globals on each call."""
return _downloads_task_worker.TaskWorkerDeps(
@ -16909,6 +16956,7 @@ def _build_task_worker_deps():
attempt_download_with_candidates=_attempt_download_with_candidates,
on_download_completed=lambda b, t, success: _on_download_completed(b, t, success=success),
recover_worker_slot=_recover_worker_slot,
try_version_mismatch_fallback=_try_version_mismatch_fallback_for_worker,
)