Harden album bundle fallback flow
Delay torrent and usenet album-bundle dispatch until missing-track analysis confirms there is work to do, matching the Soulseek album flow and avoiding release downloads for already-owned albums. Clear private album-bundle staging state when a release-level source intentionally falls back to per-track mode so workers can use the normal staging/search path instead of an empty private bundle directory. Verified by user: focused downloads master tests passed, 2 passed.
This commit is contained in:
parent
93743119d9
commit
a7ca7ddfad
3 changed files with 81 additions and 20 deletions
|
|
@ -179,6 +179,8 @@ def try_dispatch(
|
|||
'phase': 'analysis',
|
||||
'album_bundle_state': 'fallback',
|
||||
'album_bundle_error': err,
|
||||
'album_bundle_private_staging': False,
|
||||
'album_bundle_staging_path': None,
|
||||
})
|
||||
return False
|
||||
logger.error("[Album Bundle] %s flow failed for '%s': %s",
|
||||
|
|
|
|||
|
|
@ -356,26 +356,6 @@ def run_full_missing_tracks_process(batch_id, playlist_id, tracks_json, deps: Ma
|
|||
if force_download_all:
|
||||
logger.warning(f"[Force Download] Force download mode enabled for batch {batch_id} - treating all tracks as missing")
|
||||
|
||||
# Album-bundle gate for torrent / usenet single-source mode.
|
||||
# See ``core/downloads/album_bundle_dispatch`` for the full
|
||||
# narrow-gate rationale. Returns True iff the master worker
|
||||
# should stop (gate fired and failed); False = engaged-and-
|
||||
# succeeded OR didn't engage, both fall through to per-track.
|
||||
_bundle_state = _BatchStateAccessImpl()
|
||||
_album_bundle_source = _resolve_album_bundle_source(deps.config_manager)
|
||||
if _album_bundle_source and _album_bundle_source != 'soulseek':
|
||||
if _album_bundle_dispatch.try_dispatch(
|
||||
batch_id=batch_id,
|
||||
is_album=batch_is_album,
|
||||
album_context=batch_album_context,
|
||||
artist_context=batch_artist_context,
|
||||
config_get=deps.config_manager.get,
|
||||
plugin_resolver=deps.download_orchestrator.client,
|
||||
state=_bundle_state,
|
||||
source_override=_album_bundle_source,
|
||||
):
|
||||
return
|
||||
|
||||
# Allow duplicate tracks across albums — when enabled, only skip tracks already
|
||||
# owned in THIS album, not tracks owned in other albums
|
||||
allow_duplicates = deps.config_manager.get('wishlist.allow_duplicate_tracks', True)
|
||||
|
|
@ -671,6 +651,25 @@ def run_full_missing_tracks_process(batch_id, playlist_id, tracks_json, deps: Ma
|
|||
batch_playlist_folder_mode = batch.get('playlist_folder_mode', False)
|
||||
batch_playlist_name = batch.get('playlist_name', 'Unknown Playlist')
|
||||
|
||||
# Album-bundle sources download a whole release into private staging,
|
||||
# then the normal per-track workers claim those staged files. Run this
|
||||
# only after analysis has found missing tracks; otherwise an already
|
||||
# owned album would still trigger a release download.
|
||||
_bundle_state = _BatchStateAccessImpl()
|
||||
_album_bundle_source = _resolve_album_bundle_source(deps.config_manager)
|
||||
if _album_bundle_source and _album_bundle_source != 'soulseek':
|
||||
if _album_bundle_dispatch.try_dispatch(
|
||||
batch_id=batch_id,
|
||||
is_album=batch_is_album,
|
||||
album_context=batch_album_context,
|
||||
artist_context=batch_artist_context,
|
||||
config_get=deps.config_manager.get,
|
||||
plugin_resolver=deps.download_orchestrator.client,
|
||||
state=_bundle_state,
|
||||
source_override=_album_bundle_source,
|
||||
):
|
||||
return
|
||||
|
||||
# === ALBUM PRE-FLIGHT: Search for complete album folder before track-by-track ===
|
||||
# Only run pre-flight when Soulseek is the download source (or hybrid with soulseek)
|
||||
preflight_source = None
|
||||
|
|
|
|||
|
|
@ -526,6 +526,36 @@ def test_no_missing_with_auto_wishlist_submits_completion(monkeypatch):
|
|||
assert args == ('B7',)
|
||||
|
||||
|
||||
def test_no_missing_album_does_not_dispatch_torrent_bundle(monkeypatch):
|
||||
"""Release-level sources must wait until analysis confirms missing tracks."""
|
||||
album = _DBAlbum(id_=42, title='Test Album')
|
||||
db = _FakeDB(album=album, album_tracks=[_DBTrack('T1')])
|
||||
monkeypatch.setattr('database.music_database.MusicDatabase', lambda: db)
|
||||
|
||||
plugin = _FakeAlbumBundleSoulseek()
|
||||
deps = _build_deps(
|
||||
config=_FakeConfig({'download_source.mode': 'torrent'}),
|
||||
soulseek=_FakePluginWrapper({'torrent': plugin}),
|
||||
)
|
||||
_seed_batch(
|
||||
'B7a',
|
||||
is_album_download=True,
|
||||
album_context={'name': 'Test Album', 'total_tracks': 1},
|
||||
artist_context={'name': 'Artist'},
|
||||
)
|
||||
|
||||
mw.run_full_missing_tracks_process(
|
||||
'B7a',
|
||||
'album:1',
|
||||
[{'name': 'T1', 'artists': ['Artist'], 'track_number': 1}],
|
||||
deps,
|
||||
)
|
||||
|
||||
assert plugin.calls == []
|
||||
assert download_batches['B7a']['phase'] == 'complete'
|
||||
assert 'album_bundle_source' not in download_batches['B7a']
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Album fast path
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -862,6 +892,36 @@ def test_hybrid_first_torrent_uses_album_bundle_before_per_track(monkeypatch):
|
|||
assert download_batches['B27']['album_bundle_source'] == 'torrent'
|
||||
|
||||
|
||||
def test_album_bundle_fallback_clears_private_staging(monkeypatch):
|
||||
db = _FakeDB()
|
||||
monkeypatch.setattr('database.music_database.MusicDatabase', lambda: db)
|
||||
|
||||
plugin = _FakeAlbumBundleSoulseek({
|
||||
'success': False,
|
||||
'fallback': True,
|
||||
'error': 'No release passed validation',
|
||||
})
|
||||
deps = _build_deps(
|
||||
config=_FakeConfig({'download_source.mode': 'torrent'}),
|
||||
soulseek=_FakePluginWrapper({'torrent': plugin}),
|
||||
)
|
||||
_seed_batch(
|
||||
'B29',
|
||||
is_album_download=True,
|
||||
album_context={'name': 'Test Album', 'total_tracks': 1},
|
||||
artist_context={'name': 'Artist'},
|
||||
)
|
||||
tracks = [{'name': 'T1', 'artists': ['Artist'], 'track_number': 1}]
|
||||
|
||||
mw.run_full_missing_tracks_process('B29', 'album:1', tracks, deps)
|
||||
|
||||
assert len(plugin.calls) == 1
|
||||
assert download_batches['B29']['album_bundle_state'] == 'fallback'
|
||||
assert download_batches['B29']['album_bundle_private_staging'] is False
|
||||
assert download_batches['B29']['album_bundle_staging_path'] is None
|
||||
assert len(download_batches['B29']['queue']) == 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Task creation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue