Add cancellation support to all discovery workers

This commit is contained in:
Broque Thomas 2026-03-08 18:12:37 -07:00
parent 41edb31e07
commit 5f94352b40
3 changed files with 45 additions and 4 deletions

View file

@ -238,9 +238,20 @@ class PlaylistSyncService:
logger.warning(f"❌ Track {i+1} invalid for playlist: {track} (type: {type(track)}, has ratingKey: {hasattr(track, 'ratingKey') if track else 'N/A'})")
logger.info(f"Playlist validation: {len(valid_tracks)}/{len(media_tracks)} tracks are valid {server_type.title()} objects with ratingKeys")
# Use the validated tracks for the sync
plex_tracks = valid_tracks # Keep variable name for compatibility with the rest of the function
# Deduplicate by ratingKey — media servers silently drop duplicates,
# so count should reflect what actually ends up in the playlist
seen_keys = set()
deduped_tracks = []
for t in valid_tracks:
if t.ratingKey not in seen_keys:
seen_keys.add(t.ratingKey)
deduped_tracks.append(t)
if len(deduped_tracks) < len(valid_tracks):
logger.info(f"Deduplicated {len(valid_tracks) - len(deduped_tracks)} duplicate ratingKeys ({len(valid_tracks)}{len(deduped_tracks)} tracks)")
# Use the deduplicated tracks for the sync
plex_tracks = deduped_tracks
# Use active media server for playlist sync
media_client, server_type = self._get_active_media_client()

View file

@ -20852,6 +20852,8 @@ def update_tidal_playlist_phase(playlist_id):
return jsonify({"error": str(e)}), 500
_playlist_discovery_cancelled = set() # Set of automation_ids that have been cancelled
def _run_playlist_discovery_worker(playlists, automation_id=None):
"""Background worker that discovers Spotify/iTunes metadata for undiscovered
mirrored playlist tracks. Stores results in extra_data for use by sync."""
@ -20931,6 +20933,16 @@ def _run_playlist_discovery_worker(playlists, automation_id=None):
log_line=f'{len(undiscovered_tracks)} tracks to discover', log_type='info')
for i, track in enumerate(undiscovered_tracks):
# Check for cancellation
if automation_id and automation_id in _playlist_discovery_cancelled:
_playlist_discovery_cancelled.discard(automation_id)
print(f"🛑 Playlist discovery cancelled (automation {automation_id})")
_update_automation_progress(automation_id, status='finished', progress=100,
phase='Discovery cancelled',
log_line=f'Cancelled: {total_discovered} discovered, {total_failed} failed',
log_type='info')
return
total_tracks += 1
track_id = track['id']
track_name = track.get('track_name', '')
@ -21995,6 +22007,11 @@ def _run_youtube_discovery_worker(url_hash):
# Process each track for discovery
for i, track in enumerate(tracks):
try:
# Check for cancellation (phase changed by reset/delete/close)
if state.get('phase') != 'discovering':
print(f"🛑 Discovery cancelled for {url_hash} (phase changed to '{state.get('phase')}')")
return
# Update progress
state['discovery_progress'] = int((i / len(tracks)) * 100)
@ -22297,6 +22314,11 @@ def _run_listenbrainz_discovery_worker(playlist_mbid):
# Process each track for discovery
for i, track in enumerate(tracks):
try:
# Check for cancellation
if state.get('phase') != 'discovering':
print(f"🛑 ListenBrainz discovery cancelled (phase changed to '{state.get('phase')}')")
return
# Update progress
state['discovery_progress'] = int((i / len(tracks)) * 100)
@ -29417,6 +29439,11 @@ def _run_beatport_discovery_worker(url_hash):
# Process each track for discovery
for i, track in enumerate(tracks):
try:
# Check for cancellation
if state.get('phase') != 'discovering':
print(f"🛑 Beatport discovery cancelled (phase changed to '{state.get('phase')}')")
return
# Update progress
state['discovery_progress'] = int((i / len(tracks)) * 100)

View file

@ -43112,8 +43112,11 @@ async function clearMirroredDiscovery(playlistId, name) {
const data = await res.json();
if (data.success) {
showToast(`Cleared discovery for ${name} (${data.cleared} tracks)`, 'success');
// Also clear the discovery state and remove stale modal DOM
// Signal cancellation to any running worker, then clear state
const hash = `mirrored_${playlistId}`;
if (youtubePlaylistStates[hash]) {
youtubePlaylistStates[hash].phase = 'cancelled';
}
delete youtubePlaylistStates[hash];
const staleModal = document.getElementById(`youtube-discovery-modal-${hash}`);
if (staleModal) staleModal.remove();