Add cancellation support to all discovery workers
This commit is contained in:
parent
41edb31e07
commit
5f94352b40
3 changed files with 45 additions and 4 deletions
|
|
@ -239,8 +239,19 @@ class PlaylistSyncService:
|
||||||
|
|
||||||
logger.info(f"Playlist validation: {len(valid_tracks)}/{len(media_tracks)} tracks are valid {server_type.title()} objects with ratingKeys")
|
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
|
# Deduplicate by ratingKey — media servers silently drop duplicates,
|
||||||
plex_tracks = valid_tracks # Keep variable name for compatibility with the rest of the function
|
# 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
|
# Use active media server for playlist sync
|
||||||
media_client, server_type = self._get_active_media_client()
|
media_client, server_type = self._get_active_media_client()
|
||||||
|
|
|
||||||
|
|
@ -20852,6 +20852,8 @@ def update_tidal_playlist_phase(playlist_id):
|
||||||
return jsonify({"error": str(e)}), 500
|
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):
|
def _run_playlist_discovery_worker(playlists, automation_id=None):
|
||||||
"""Background worker that discovers Spotify/iTunes metadata for undiscovered
|
"""Background worker that discovers Spotify/iTunes metadata for undiscovered
|
||||||
mirrored playlist tracks. Stores results in extra_data for use by sync."""
|
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')
|
log_line=f'{len(undiscovered_tracks)} tracks to discover', log_type='info')
|
||||||
|
|
||||||
for i, track in enumerate(undiscovered_tracks):
|
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
|
total_tracks += 1
|
||||||
track_id = track['id']
|
track_id = track['id']
|
||||||
track_name = track.get('track_name', '')
|
track_name = track.get('track_name', '')
|
||||||
|
|
@ -21995,6 +22007,11 @@ def _run_youtube_discovery_worker(url_hash):
|
||||||
# Process each track for discovery
|
# Process each track for discovery
|
||||||
for i, track in enumerate(tracks):
|
for i, track in enumerate(tracks):
|
||||||
try:
|
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
|
# Update progress
|
||||||
state['discovery_progress'] = int((i / len(tracks)) * 100)
|
state['discovery_progress'] = int((i / len(tracks)) * 100)
|
||||||
|
|
||||||
|
|
@ -22297,6 +22314,11 @@ def _run_listenbrainz_discovery_worker(playlist_mbid):
|
||||||
# Process each track for discovery
|
# Process each track for discovery
|
||||||
for i, track in enumerate(tracks):
|
for i, track in enumerate(tracks):
|
||||||
try:
|
try:
|
||||||
|
# Check for cancellation
|
||||||
|
if state.get('phase') != 'discovering':
|
||||||
|
print(f"🛑 ListenBrainz discovery cancelled (phase changed to '{state.get('phase')}')")
|
||||||
|
return
|
||||||
|
|
||||||
# Update progress
|
# Update progress
|
||||||
state['discovery_progress'] = int((i / len(tracks)) * 100)
|
state['discovery_progress'] = int((i / len(tracks)) * 100)
|
||||||
|
|
||||||
|
|
@ -29417,6 +29439,11 @@ def _run_beatport_discovery_worker(url_hash):
|
||||||
# Process each track for discovery
|
# Process each track for discovery
|
||||||
for i, track in enumerate(tracks):
|
for i, track in enumerate(tracks):
|
||||||
try:
|
try:
|
||||||
|
# Check for cancellation
|
||||||
|
if state.get('phase') != 'discovering':
|
||||||
|
print(f"🛑 Beatport discovery cancelled (phase changed to '{state.get('phase')}')")
|
||||||
|
return
|
||||||
|
|
||||||
# Update progress
|
# Update progress
|
||||||
state['discovery_progress'] = int((i / len(tracks)) * 100)
|
state['discovery_progress'] = int((i / len(tracks)) * 100)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43112,8 +43112,11 @@ async function clearMirroredDiscovery(playlistId, name) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
showToast(`Cleared discovery for ${name} (${data.cleared} tracks)`, '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}`;
|
const hash = `mirrored_${playlistId}`;
|
||||||
|
if (youtubePlaylistStates[hash]) {
|
||||||
|
youtubePlaylistStates[hash].phase = 'cancelled';
|
||||||
|
}
|
||||||
delete youtubePlaylistStates[hash];
|
delete youtubePlaylistStates[hash];
|
||||||
const staleModal = document.getElementById(`youtube-discovery-modal-${hash}`);
|
const staleModal = document.getElementById(`youtube-discovery-modal-${hash}`);
|
||||||
if (staleModal) staleModal.remove();
|
if (staleModal) staleModal.remove();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue