Discovery lift (2/N): cancel_*_sync + delete_*_playlist -> shared helpers
Second cluster. Two more sets of byte-identical per-source bodies: cancel_<source>_sync (Tidal, Deezer, Qobuz, Spotify-Public, iTunes-Link, YouTube, ListenBrainz) -> core.discovery.endpoints.cancel_sync(states, key, *, label, not_found_message, sync_lock, sync_states, active_sync_workers). Returns (payload, status_code); a thin web_server glue (_cancel_source_sync) wires the sync-infra globals + jsonify. Caller passes the resolved key (ListenBrainz transforms via _lb_state_key) and the exact 404 string (iTunes-Link uses "iTunes Link not found"). delete_<source>_playlist (Tidal, Deezer, Qobuz, Spotify-Public) -> delete_playlist_state(states, key, *, label, not_found_message), wired via _delete_source_playlist. Intentionally left with their own bodies (genuinely divergent, not 1:1): - Beatport cancel (cancels a stored sync_future, no message, warning log). - iTunes-Link / YouTube / ListenBrainz / Beatport deletes (different success messages, info-log wording, playlist-name extraction, /remove route, chart key). Tests: +11 in tests/discovery/test_discovery_endpoints.py covering cancel (404, active-worker cancel + state revert, worker-absent, no-sync-in-progress, label in message, exception->500) and delete (404, future cancel + removal, no/falsy future, exception->500 leaves state). Full discovery suite: 162 passed. web_server.py: -216 lines.
This commit is contained in:
parent
628395eda5
commit
2d76a7c061
3 changed files with 255 additions and 254 deletions
|
|
@ -14,7 +14,8 @@ shape) is intentionally NOT routed through here and stays in its own function.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List
|
||||
import time
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
|
|
@ -74,3 +75,85 @@ def convert_results_to_spotify_tracks(
|
|||
|
||||
logger.info(f"Converted {len(spotify_tracks)} {source_label} matches to Spotify tracks for sync")
|
||||
return spotify_tracks
|
||||
|
||||
|
||||
def cancel_sync(
|
||||
states: Dict[str, Any],
|
||||
key: str,
|
||||
*,
|
||||
label: str,
|
||||
not_found_message: str,
|
||||
sync_lock: Any,
|
||||
sync_states: Dict[str, Any],
|
||||
active_sync_workers: Dict[str, Any],
|
||||
) -> Tuple[Dict[str, Any], int]:
|
||||
"""Cancel an in-progress sync for one discovery playlist.
|
||||
|
||||
1:1 lift of the byte-identical ``cancel_<source>_sync`` bodies (Tidal,
|
||||
Deezer, Qobuz, Spotify-Public, iTunes-Link, YouTube, ListenBrainz). The
|
||||
caller passes the already-resolved state key (ListenBrainz transforms it
|
||||
via ``_lb_state_key`` first), the source ``label``, the exact 404 message
|
||||
(iTunes-Link uses "iTunes Link not found", not "... playlist not found"),
|
||||
and the shared sync infrastructure (so this stays free of web_server
|
||||
globals / Flask).
|
||||
|
||||
Returns ``(payload_dict, status_code)``; the caller wraps in ``jsonify``.
|
||||
|
||||
Beatport is NOT routed here — it cancels a stored ``sync_future`` and
|
||||
returns a different payload.
|
||||
"""
|
||||
try:
|
||||
if key not in states:
|
||||
return {"error": not_found_message}, 404
|
||||
|
||||
state = states[key]
|
||||
state['last_accessed'] = time.time()
|
||||
sync_playlist_id = state.get('sync_playlist_id')
|
||||
|
||||
if sync_playlist_id:
|
||||
with sync_lock:
|
||||
sync_states[sync_playlist_id] = {"status": "cancelled"}
|
||||
if sync_playlist_id in active_sync_workers:
|
||||
del active_sync_workers[sync_playlist_id]
|
||||
|
||||
state['phase'] = 'discovered'
|
||||
state['sync_playlist_id'] = None
|
||||
state['sync_progress'] = {}
|
||||
|
||||
return {"success": True, "message": f"{label} sync cancelled"}, 200
|
||||
except Exception as e:
|
||||
logger.error(f"Error cancelling {label} sync: {e}")
|
||||
return {"error": str(e)}, 500
|
||||
|
||||
|
||||
def delete_playlist_state(
|
||||
states: Dict[str, Any],
|
||||
key: str,
|
||||
*,
|
||||
label: str,
|
||||
not_found_message: str,
|
||||
) -> Tuple[Dict[str, Any], int]:
|
||||
"""Delete a discovery playlist's state entry, cancelling any active
|
||||
discovery first.
|
||||
|
||||
1:1 lift of the byte-identical ``delete_<source>_playlist`` bodies
|
||||
(Tidal, Deezer, Qobuz, Spotify-Public). Returns ``(payload, status_code)``.
|
||||
|
||||
The iTunes-Link / YouTube / ListenBrainz / Beatport deletes intentionally
|
||||
keep their own bodies — they differ in success message, info-log wording,
|
||||
name extraction, and/or key transform.
|
||||
"""
|
||||
try:
|
||||
if key not in states:
|
||||
return {"error": not_found_message}, 404
|
||||
|
||||
state = states[key]
|
||||
if 'discovery_future' in state and state['discovery_future']:
|
||||
state['discovery_future'].cancel()
|
||||
del states[key]
|
||||
|
||||
logger.info(f"Deleted {label} playlist state: {key}")
|
||||
return {"success": True, "message": "Playlist deleted"}, 200
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting {label} playlist: {e}")
|
||||
return {"error": str(e)}, 500
|
||||
|
|
|
|||
|
|
@ -8,7 +8,30 @@ the originals handled is exercised here.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from core.discovery.endpoints import convert_results_to_spotify_tracks
|
||||
import threading
|
||||
|
||||
from core.discovery.endpoints import (
|
||||
convert_results_to_spotify_tracks,
|
||||
cancel_sync,
|
||||
delete_playlist_state,
|
||||
)
|
||||
|
||||
|
||||
class _FakeFuture:
|
||||
def __init__(self):
|
||||
self.cancelled = False
|
||||
|
||||
def cancel(self):
|
||||
self.cancelled = True
|
||||
|
||||
|
||||
def _cancel_infra():
|
||||
"""Fresh sync infra (lock + the two shared dicts) for cancel_sync tests."""
|
||||
return {
|
||||
'sync_lock': threading.Lock(),
|
||||
'sync_states': {},
|
||||
'active_sync_workers': {},
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -125,3 +148,114 @@ def test_spotify_data_takes_precedence_over_auto_fields():
|
|||
}]
|
||||
out = convert_results_to_spotify_tracks(results, 'Tidal')
|
||||
assert out[0]['id'] == 'D'
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# cancel_sync
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_cancel_sync_not_found_returns_404():
|
||||
body, code = cancel_sync({}, 'missing', label='Tidal',
|
||||
not_found_message='Tidal playlist not found', **_cancel_infra())
|
||||
assert code == 404
|
||||
assert body == {"error": "Tidal playlist not found"}
|
||||
|
||||
|
||||
def test_cancel_sync_cancels_active_worker_and_reverts_state():
|
||||
infra = _cancel_infra()
|
||||
infra['sync_states']['sp1'] = {"status": "running"}
|
||||
infra['active_sync_workers']['sp1'] = _FakeFuture()
|
||||
states = {'pl': {'phase': 'syncing', 'sync_playlist_id': 'sp1',
|
||||
'sync_progress': {'done': 1}, 'last_accessed': 0}}
|
||||
|
||||
body, code = cancel_sync(states, 'pl', label='Tidal',
|
||||
not_found_message='nf', **infra)
|
||||
|
||||
assert code == 200
|
||||
assert body == {"success": True, "message": "Tidal sync cancelled"}
|
||||
# sync marked cancelled, worker removed
|
||||
assert infra['sync_states']['sp1'] == {"status": "cancelled"}
|
||||
assert 'sp1' not in infra['active_sync_workers']
|
||||
# state reverted
|
||||
assert states['pl']['phase'] == 'discovered'
|
||||
assert states['pl']['sync_playlist_id'] is None
|
||||
assert states['pl']['sync_progress'] == {}
|
||||
assert states['pl']['last_accessed'] != 0 # touched
|
||||
|
||||
|
||||
def test_cancel_sync_worker_absent_from_active_map_is_safe():
|
||||
infra = _cancel_infra() # active_sync_workers empty
|
||||
states = {'pl': {'phase': 'syncing', 'sync_playlist_id': 'sp1'}}
|
||||
body, code = cancel_sync(states, 'pl', label='Deezer', not_found_message='nf', **infra)
|
||||
assert code == 200
|
||||
assert infra['sync_states']['sp1'] == {"status": "cancelled"}
|
||||
|
||||
|
||||
def test_cancel_sync_no_sync_in_progress_still_reverts():
|
||||
infra = _cancel_infra()
|
||||
states = {'pl': {'phase': 'discovered'}} # no sync_playlist_id
|
||||
body, code = cancel_sync(states, 'pl', label='Qobuz', not_found_message='nf', **infra)
|
||||
assert code == 200
|
||||
assert states['pl']['sync_playlist_id'] is None
|
||||
assert states['pl']['sync_progress'] == {}
|
||||
assert infra['sync_states'] == {} # nothing cancelled
|
||||
|
||||
|
||||
def test_cancel_sync_label_in_message():
|
||||
infra = _cancel_infra()
|
||||
states = {'pl': {}}
|
||||
body, _ = cancel_sync(states, 'pl', label='iTunes Link', not_found_message='nf', **infra)
|
||||
assert body["message"] == "iTunes Link sync cancelled"
|
||||
|
||||
|
||||
def test_cancel_sync_exception_returns_500():
|
||||
infra = _cancel_infra()
|
||||
states = {'pl': object()} # not subscriptable -> raises inside try
|
||||
body, code = cancel_sync(states, 'pl', label='Tidal', not_found_message='nf', **infra)
|
||||
assert code == 500
|
||||
assert "error" in body
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# delete_playlist_state
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_delete_not_found_returns_404():
|
||||
body, code = delete_playlist_state({}, 'missing', label='Tidal',
|
||||
not_found_message='Tidal playlist not found')
|
||||
assert code == 404
|
||||
assert body == {"error": "Tidal playlist not found"}
|
||||
|
||||
|
||||
def test_delete_cancels_discovery_future_and_removes_state():
|
||||
fut = _FakeFuture()
|
||||
states = {'pl': {'discovery_future': fut}}
|
||||
body, code = delete_playlist_state(states, 'pl', label='Tidal', not_found_message='nf')
|
||||
assert code == 200
|
||||
assert body == {"success": True, "message": "Playlist deleted"}
|
||||
assert fut.cancelled is True
|
||||
assert 'pl' not in states
|
||||
|
||||
|
||||
def test_delete_without_discovery_future_still_removes():
|
||||
states = {'pl': {'phase': 'discovered'}} # no discovery_future
|
||||
body, code = delete_playlist_state(states, 'pl', label='Deezer', not_found_message='nf')
|
||||
assert code == 200
|
||||
assert 'pl' not in states
|
||||
|
||||
|
||||
def test_delete_falsy_discovery_future_not_cancelled():
|
||||
states = {'pl': {'discovery_future': None}}
|
||||
body, code = delete_playlist_state(states, 'pl', label='Qobuz', not_found_message='nf')
|
||||
assert code == 200
|
||||
assert 'pl' not in states
|
||||
|
||||
|
||||
def test_delete_exception_returns_500():
|
||||
fut = object() # no .cancel() -> AttributeError inside try
|
||||
states = {'pl': {'discovery_future': fut}}
|
||||
body, code = delete_playlist_state(states, 'pl', label='Tidal', not_found_message='nf')
|
||||
assert code == 500
|
||||
assert "error" in body
|
||||
# state NOT deleted because the exception fired before del
|
||||
assert 'pl' in states
|
||||
|
|
|
|||
288
web_server.py
288
web_server.py
|
|
@ -20802,25 +20802,7 @@ def reset_tidal_playlist(playlist_id):
|
|||
@app.route('/api/tidal/delete/<playlist_id>', methods=['POST'])
|
||||
def delete_tidal_playlist(playlist_id):
|
||||
"""Delete Tidal playlist state completely"""
|
||||
try:
|
||||
if playlist_id not in tidal_discovery_states:
|
||||
return jsonify({"error": "Tidal playlist not found"}), 404
|
||||
|
||||
state = tidal_discovery_states[playlist_id]
|
||||
|
||||
# Stop any active discovery
|
||||
if 'discovery_future' in state and state['discovery_future']:
|
||||
state['discovery_future'].cancel()
|
||||
|
||||
# Remove from state dictionary
|
||||
del tidal_discovery_states[playlist_id]
|
||||
|
||||
logger.info(f"Deleted Tidal playlist state: {playlist_id}")
|
||||
return jsonify({"success": True, "message": "Playlist deleted"})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting Tidal playlist: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _delete_source_playlist(tidal_discovery_states, playlist_id, "Tidal", "Tidal playlist not found")
|
||||
|
||||
@app.route('/api/tidal/update_phase/<playlist_id>', methods=['POST'])
|
||||
def update_tidal_playlist_phase(playlist_id):
|
||||
|
|
@ -21074,7 +21056,31 @@ from core.discovery.scoring import (
|
|||
# Tidal discovery worker logic lives in core/discovery/tidal.py.
|
||||
from core.discovery import tidal as _discovery_tidal
|
||||
# Source-agnostic discovery route helpers (lifted from the per-source copies).
|
||||
from core.discovery.endpoints import convert_results_to_spotify_tracks
|
||||
from core.discovery.endpoints import (
|
||||
convert_results_to_spotify_tracks,
|
||||
cancel_sync as _cancel_sync_core,
|
||||
delete_playlist_state as _delete_playlist_state_core,
|
||||
)
|
||||
|
||||
|
||||
def _cancel_source_sync(states, key, label, not_found_message):
|
||||
"""Thin glue: wire web_server's sync infra into the lifted cancel_sync
|
||||
helper and jsonify the result. Used by the per-source cancel routes."""
|
||||
body, code = _cancel_sync_core(
|
||||
states, key, label=label, not_found_message=not_found_message,
|
||||
sync_lock=sync_lock, sync_states=sync_states,
|
||||
active_sync_workers=active_sync_workers,
|
||||
)
|
||||
return jsonify(body), code
|
||||
|
||||
|
||||
def _delete_source_playlist(states, key, label, not_found_message):
|
||||
"""Thin glue for the per-source delete routes that share the identical
|
||||
delete body (Tidal/Deezer/Qobuz/Spotify-Public)."""
|
||||
body, code = _delete_playlist_state_core(
|
||||
states, key, label=label, not_found_message=not_found_message,
|
||||
)
|
||||
return jsonify(body), code
|
||||
|
||||
|
||||
def _build_tidal_discovery_deps():
|
||||
|
|
@ -21214,33 +21220,7 @@ def get_tidal_sync_status(playlist_id):
|
|||
@app.route('/api/tidal/sync/cancel/<playlist_id>', methods=['POST'])
|
||||
def cancel_tidal_sync(playlist_id):
|
||||
"""Cancel sync for a Tidal playlist"""
|
||||
try:
|
||||
if playlist_id not in tidal_discovery_states:
|
||||
return jsonify({"error": "Tidal playlist not found"}), 404
|
||||
|
||||
state = tidal_discovery_states[playlist_id]
|
||||
state['last_accessed'] = time.time() # Update access time
|
||||
sync_playlist_id = state.get('sync_playlist_id')
|
||||
|
||||
if sync_playlist_id:
|
||||
# Cancel the sync using existing sync infrastructure
|
||||
with sync_lock:
|
||||
sync_states[sync_playlist_id] = {"status": "cancelled"}
|
||||
|
||||
# Clean up sync worker
|
||||
if sync_playlist_id in active_sync_workers:
|
||||
del active_sync_workers[sync_playlist_id]
|
||||
|
||||
# Revert Tidal state
|
||||
state['phase'] = 'discovered'
|
||||
state['sync_playlist_id'] = None
|
||||
state['sync_progress'] = {}
|
||||
|
||||
return jsonify({"success": True, "message": "Tidal sync cancelled"})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error cancelling Tidal sync: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _cancel_source_sync(tidal_discovery_states, playlist_id, "Tidal", "Tidal playlist not found")
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
|
@ -21699,25 +21679,7 @@ def reset_deezer_playlist(playlist_id):
|
|||
@app.route('/api/deezer/delete/<playlist_id>', methods=['POST'])
|
||||
def delete_deezer_playlist(playlist_id):
|
||||
"""Delete Deezer playlist state completely"""
|
||||
try:
|
||||
if playlist_id not in deezer_discovery_states:
|
||||
return jsonify({"error": "Deezer playlist not found"}), 404
|
||||
|
||||
state = deezer_discovery_states[playlist_id]
|
||||
|
||||
# Stop any active discovery
|
||||
if 'discovery_future' in state and state['discovery_future']:
|
||||
state['discovery_future'].cancel()
|
||||
|
||||
# Remove from state dictionary
|
||||
del deezer_discovery_states[playlist_id]
|
||||
|
||||
logger.info(f"Deleted Deezer playlist state: {playlist_id}")
|
||||
return jsonify({"success": True, "message": "Playlist deleted"})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting Deezer playlist: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _delete_source_playlist(deezer_discovery_states, playlist_id, "Deezer", "Deezer playlist not found")
|
||||
|
||||
@app.route('/api/deezer/update_phase/<playlist_id>', methods=['POST'])
|
||||
def update_deezer_playlist_phase(playlist_id):
|
||||
|
|
@ -21893,33 +21855,7 @@ def get_deezer_sync_status(playlist_id):
|
|||
@app.route('/api/deezer/sync/cancel/<playlist_id>', methods=['POST'])
|
||||
def cancel_deezer_sync(playlist_id):
|
||||
"""Cancel sync for a Deezer playlist"""
|
||||
try:
|
||||
if playlist_id not in deezer_discovery_states:
|
||||
return jsonify({"error": "Deezer playlist not found"}), 404
|
||||
|
||||
state = deezer_discovery_states[playlist_id]
|
||||
state['last_accessed'] = time.time()
|
||||
sync_playlist_id = state.get('sync_playlist_id')
|
||||
|
||||
if sync_playlist_id:
|
||||
# Cancel the sync using existing sync infrastructure
|
||||
with sync_lock:
|
||||
sync_states[sync_playlist_id] = {"status": "cancelled"}
|
||||
|
||||
# Clean up sync worker
|
||||
if sync_playlist_id in active_sync_workers:
|
||||
del active_sync_workers[sync_playlist_id]
|
||||
|
||||
# Revert Deezer state
|
||||
state['phase'] = 'discovered'
|
||||
state['sync_playlist_id'] = None
|
||||
state['sync_progress'] = {}
|
||||
|
||||
return jsonify({"success": True, "message": "Deezer sync cancelled"})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error cancelling Deezer sync: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _cancel_source_sync(deezer_discovery_states, playlist_id, "Deezer", "Deezer playlist not found")
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
|
@ -22335,23 +22271,7 @@ def reset_qobuz_playlist(playlist_id):
|
|||
@app.route('/api/qobuz/delete/<playlist_id>', methods=['POST'])
|
||||
def delete_qobuz_playlist(playlist_id):
|
||||
"""Delete Qobuz playlist state completely."""
|
||||
try:
|
||||
if playlist_id not in qobuz_discovery_states:
|
||||
return jsonify({"error": "Qobuz playlist not found"}), 404
|
||||
|
||||
state = qobuz_discovery_states[playlist_id]
|
||||
|
||||
if 'discovery_future' in state and state['discovery_future']:
|
||||
state['discovery_future'].cancel()
|
||||
|
||||
del qobuz_discovery_states[playlist_id]
|
||||
|
||||
logger.info(f"Deleted Qobuz playlist state: {playlist_id}")
|
||||
return jsonify({"success": True, "message": "Playlist deleted"})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting Qobuz playlist: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _delete_source_playlist(qobuz_discovery_states, playlist_id, "Qobuz", "Qobuz playlist not found")
|
||||
|
||||
|
||||
@app.route('/api/qobuz/update_phase/<playlist_id>', methods=['POST'])
|
||||
|
|
@ -22517,29 +22437,7 @@ def get_qobuz_sync_status(playlist_id):
|
|||
@app.route('/api/qobuz/sync/cancel/<playlist_id>', methods=['POST'])
|
||||
def cancel_qobuz_sync(playlist_id):
|
||||
"""Cancel sync for a Qobuz playlist."""
|
||||
try:
|
||||
if playlist_id not in qobuz_discovery_states:
|
||||
return jsonify({"error": "Qobuz playlist not found"}), 404
|
||||
|
||||
state = qobuz_discovery_states[playlist_id]
|
||||
state['last_accessed'] = time.time()
|
||||
sync_playlist_id = state.get('sync_playlist_id')
|
||||
|
||||
if sync_playlist_id:
|
||||
with sync_lock:
|
||||
sync_states[sync_playlist_id] = {"status": "cancelled"}
|
||||
if sync_playlist_id in active_sync_workers:
|
||||
del active_sync_workers[sync_playlist_id]
|
||||
|
||||
state['phase'] = 'discovered'
|
||||
state['sync_playlist_id'] = None
|
||||
state['sync_progress'] = {}
|
||||
|
||||
return jsonify({"success": True, "message": "Qobuz sync cancelled"})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error cancelling Qobuz sync: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _cancel_source_sync(qobuz_discovery_states, playlist_id, "Qobuz", "Qobuz playlist not found")
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
|
@ -23239,25 +23137,7 @@ def reset_spotify_public_playlist(url_hash):
|
|||
@app.route('/api/spotify-public/delete/<url_hash>', methods=['POST'])
|
||||
def delete_spotify_public_playlist(url_hash):
|
||||
"""Delete Spotify Public playlist state completely"""
|
||||
try:
|
||||
if url_hash not in spotify_public_discovery_states:
|
||||
return jsonify({"error": "Spotify Public playlist not found"}), 404
|
||||
|
||||
state = spotify_public_discovery_states[url_hash]
|
||||
|
||||
# Stop any active discovery
|
||||
if 'discovery_future' in state and state['discovery_future']:
|
||||
state['discovery_future'].cancel()
|
||||
|
||||
# Remove from state dictionary
|
||||
del spotify_public_discovery_states[url_hash]
|
||||
|
||||
logger.info(f"Deleted Spotify Public playlist state: {url_hash}")
|
||||
return jsonify({"success": True, "message": "Playlist deleted"})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting Spotify Public playlist: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _delete_source_playlist(spotify_public_discovery_states, url_hash, "Spotify Public", "Spotify Public playlist not found")
|
||||
|
||||
@app.route('/api/spotify-public/update_phase/<url_hash>', methods=['POST'])
|
||||
def update_spotify_public_playlist_phase(url_hash):
|
||||
|
|
@ -23434,33 +23314,7 @@ def get_spotify_public_sync_status(url_hash):
|
|||
@app.route('/api/spotify-public/sync/cancel/<url_hash>', methods=['POST'])
|
||||
def cancel_spotify_public_sync(url_hash):
|
||||
"""Cancel sync for a Spotify Public playlist"""
|
||||
try:
|
||||
if url_hash not in spotify_public_discovery_states:
|
||||
return jsonify({"error": "Spotify Public playlist not found"}), 404
|
||||
|
||||
state = spotify_public_discovery_states[url_hash]
|
||||
state['last_accessed'] = time.time()
|
||||
sync_playlist_id = state.get('sync_playlist_id')
|
||||
|
||||
if sync_playlist_id:
|
||||
# Cancel the sync using existing sync infrastructure
|
||||
with sync_lock:
|
||||
sync_states[sync_playlist_id] = {"status": "cancelled"}
|
||||
|
||||
# Clean up sync worker
|
||||
if sync_playlist_id in active_sync_workers:
|
||||
del active_sync_workers[sync_playlist_id]
|
||||
|
||||
# Revert Spotify Public state
|
||||
state['phase'] = 'discovered'
|
||||
state['sync_playlist_id'] = None
|
||||
state['sync_progress'] = {}
|
||||
|
||||
return jsonify({"success": True, "message": "Spotify Public sync cancelled"})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error cancelling Spotify Public sync: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _cancel_source_sync(spotify_public_discovery_states, url_hash, "Spotify Public", "Spotify Public playlist not found")
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
|
@ -23890,24 +23744,7 @@ def get_itunes_link_sync_status(url_hash):
|
|||
|
||||
@app.route('/api/itunes-link/sync/cancel/<url_hash>', methods=['POST'])
|
||||
def cancel_itunes_link_sync(url_hash):
|
||||
try:
|
||||
if url_hash not in itunes_link_discovery_states:
|
||||
return jsonify({"error": "iTunes Link not found"}), 404
|
||||
state = itunes_link_discovery_states[url_hash]
|
||||
state['last_accessed'] = time.time()
|
||||
sync_playlist_id = state.get('sync_playlist_id')
|
||||
if sync_playlist_id:
|
||||
with sync_lock:
|
||||
sync_states[sync_playlist_id] = {"status": "cancelled"}
|
||||
if sync_playlist_id in active_sync_workers:
|
||||
del active_sync_workers[sync_playlist_id]
|
||||
state['phase'] = 'discovered'
|
||||
state['sync_playlist_id'] = None
|
||||
state['sync_progress'] = {}
|
||||
return jsonify({"success": True, "message": "iTunes Link sync cancelled"})
|
||||
except Exception as e:
|
||||
logger.error(f"Error cancelling iTunes Link sync: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _cancel_source_sync(itunes_link_discovery_states, url_hash, "iTunes Link", "iTunes Link not found")
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
|
@ -24555,33 +24392,7 @@ def get_youtube_sync_status(url_hash):
|
|||
@app.route('/api/youtube/sync/cancel/<url_hash>', methods=['POST'])
|
||||
def cancel_youtube_sync(url_hash):
|
||||
"""Cancel sync for a YouTube playlist"""
|
||||
try:
|
||||
if url_hash not in youtube_playlist_states:
|
||||
return jsonify({"error": "YouTube playlist not found"}), 404
|
||||
|
||||
state = youtube_playlist_states[url_hash]
|
||||
state['last_accessed'] = time.time() # Update access time
|
||||
sync_playlist_id = state.get('sync_playlist_id')
|
||||
|
||||
if sync_playlist_id:
|
||||
# Cancel the sync using existing sync infrastructure
|
||||
with sync_lock:
|
||||
sync_states[sync_playlist_id] = {"status": "cancelled"}
|
||||
|
||||
# Clean up sync worker
|
||||
if sync_playlist_id in active_sync_workers:
|
||||
del active_sync_workers[sync_playlist_id]
|
||||
|
||||
# Revert YouTube state
|
||||
state['phase'] = 'discovered'
|
||||
state['sync_playlist_id'] = None
|
||||
state['sync_progress'] = {}
|
||||
|
||||
return jsonify({"success": True, "message": "YouTube sync cancelled"})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error cancelling YouTube sync: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _cancel_source_sync(youtube_playlist_states, url_hash, "YouTube", "YouTube playlist not found")
|
||||
|
||||
# New YouTube Playlist Management Endpoints (for persistent state)
|
||||
|
||||
|
|
@ -30690,34 +30501,7 @@ def get_listenbrainz_sync_status(playlist_mbid):
|
|||
@app.route('/api/listenbrainz/sync/cancel/<playlist_mbid>', methods=['POST'])
|
||||
def cancel_listenbrainz_sync(playlist_mbid):
|
||||
"""Cancel sync for a ListenBrainz playlist"""
|
||||
try:
|
||||
state_key = _lb_state_key(playlist_mbid)
|
||||
if state_key not in listenbrainz_playlist_states:
|
||||
return jsonify({"error": "ListenBrainz playlist not found"}), 404
|
||||
|
||||
state = listenbrainz_playlist_states[state_key]
|
||||
state['last_accessed'] = time.time() # Update access time
|
||||
sync_playlist_id = state.get('sync_playlist_id')
|
||||
|
||||
if sync_playlist_id:
|
||||
# Cancel the sync using existing sync infrastructure
|
||||
with sync_lock:
|
||||
sync_states[sync_playlist_id] = {"status": "cancelled"}
|
||||
|
||||
# Clean up sync worker
|
||||
if sync_playlist_id in active_sync_workers:
|
||||
del active_sync_workers[sync_playlist_id]
|
||||
|
||||
# Revert ListenBrainz state
|
||||
state['phase'] = 'discovered'
|
||||
state['sync_playlist_id'] = None
|
||||
state['sync_progress'] = {}
|
||||
|
||||
return jsonify({"success": True, "message": "ListenBrainz sync cancelled"})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error cancelling ListenBrainz sync: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _cancel_source_sync(listenbrainz_playlist_states, _lb_state_key(playlist_mbid), "ListenBrainz", "ListenBrainz playlist not found")
|
||||
|
||||
@app.route('/api/metadata/start', methods=['POST'])
|
||||
def start_metadata_update():
|
||||
|
|
|
|||
Loading…
Reference in a new issue