Discovery lift (3/N): get_*_sync_status -> shared helper
Third cluster: the get_<source>_sync_status routes (Tidal, Deezer, Qobuz, Spotify-Public, iTunes-Link, YouTube, ListenBrainz) -> core.discovery. endpoints.get_sync_status(...), wired via _get_source_sync_status glue. This cluster carries the real per-source quirks, all captured 1:1 as params: - not_found_message (iTunes-Link uses "iTunes Link not found"). - error_label vs activity_subject — these DIFFER for Spotify-Public: the activity feed says "Spotify Link playlist ..." while the except log says "Error getting Spotify Public sync status". - playlist-name accessor, three styles lifted verbatim as named helpers: playlist_name_attr_or_unknown (Tidal: object .name), playlist_name_strict (Deezer/Qobuz/Spotify-Public/iTunes: state['playlist']['name'], can raise), playlist_name_safe (YouTube/ListenBrainz: .get default). The strict getter preserves the original's behavior of raising -> 500 AFTER phase/sync_progress were already mutated. - ListenBrainz key via _lb_state_key (caller-resolved). Beatport stays separate (different payload: status not sync_status, sync_id, no lock, chart key). Tests: +9 (3 name accessors incl. raise/fallback semantics; status 404s, running-no-mutation, finished+activity, error+revert+activity, and strict- getter-missing -> 500 after partial mutation). Full discovery suite: 171 passed. web_server.py: -244 lines.
This commit is contained in:
parent
2d76a7c061
commit
aad1d2b8f0
3 changed files with 250 additions and 267 deletions
|
|
@ -157,3 +157,95 @@ def delete_playlist_state(
|
|||
except Exception as e:
|
||||
logger.error(f"Error deleting {label} playlist: {e}")
|
||||
return {"error": str(e)}, 500
|
||||
|
||||
|
||||
# --- playlist-name accessors -------------------------------------------------
|
||||
# The per-source sync-status handlers read the display name three different
|
||||
# ways. Each is reproduced verbatim so the 1:1 behavior (including which ones
|
||||
# raise vs. fall back to 'Unknown Playlist') is preserved.
|
||||
|
||||
def playlist_name_attr_or_unknown(state: Dict[str, Any]) -> str:
|
||||
"""Tidal: playlist is an object — use ``.name`` or 'Unknown Playlist'."""
|
||||
pl = state.get('playlist')
|
||||
return pl.name if pl and hasattr(pl, 'name') else 'Unknown Playlist'
|
||||
|
||||
|
||||
def playlist_name_strict(state: Dict[str, Any]) -> str:
|
||||
"""Deezer / Qobuz / Spotify-Public / iTunes-Link: strict dict access —
|
||||
raises (→ 500) if 'playlist' is missing, exactly like the originals."""
|
||||
return state['playlist']['name']
|
||||
|
||||
|
||||
def playlist_name_safe(state: Dict[str, Any]) -> str:
|
||||
"""YouTube / ListenBrainz: safe dict access, defaulting to 'Unknown
|
||||
Playlist'."""
|
||||
return state.get('playlist', {}).get('name', 'Unknown Playlist')
|
||||
|
||||
|
||||
def get_sync_status(
|
||||
states: Dict[str, Any],
|
||||
key: str,
|
||||
*,
|
||||
not_found_message: str,
|
||||
error_label: str,
|
||||
activity_subject: str,
|
||||
playlist_name_getter,
|
||||
sync_lock: Any,
|
||||
sync_states: Dict[str, Any],
|
||||
add_activity_item,
|
||||
) -> Tuple[Dict[str, Any], int]:
|
||||
"""Report sync status for one discovery playlist, posting an activity-feed
|
||||
item when the sync finishes or errors.
|
||||
|
||||
1:1 lift of the ``get_<source>_sync_status`` bodies (Tidal, Deezer, Qobuz,
|
||||
Spotify-Public, iTunes-Link, YouTube, ListenBrainz). Per-source variation
|
||||
is captured by the parameters:
|
||||
|
||||
- ``not_found_message`` — the 404 string (iTunes-Link drops "playlist").
|
||||
- ``error_label`` — used in the except log ("Error getting <X> sync status").
|
||||
- ``activity_subject`` — the activity-feed prefix; note Spotify-Public uses
|
||||
"Spotify Link playlist" while its error_label is "Spotify Public".
|
||||
- ``playlist_name_getter`` — one of the accessors above (attr/strict/safe);
|
||||
the strict one can raise, matching the originals (→ 500). The state's
|
||||
phase/sync_progress are mutated BEFORE the name is read, so a raising
|
||||
getter leaves the same partial mutation the original did.
|
||||
|
||||
Beatport is NOT routed here — it returns a different payload (``status``
|
||||
not ``sync_status``, includes ``sync_id``, no lock, ``chart`` key).
|
||||
"""
|
||||
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 not sync_playlist_id:
|
||||
return {"error": "No sync in progress"}, 404
|
||||
|
||||
with sync_lock:
|
||||
sync_state = sync_states.get(sync_playlist_id, {})
|
||||
|
||||
response = {
|
||||
'phase': state['phase'],
|
||||
'sync_status': sync_state.get('status', 'unknown'),
|
||||
'progress': sync_state.get('progress', {}),
|
||||
'complete': sync_state.get('status') == 'finished',
|
||||
'error': sync_state.get('error'),
|
||||
}
|
||||
|
||||
if sync_state.get('status') == 'finished':
|
||||
state['phase'] = 'sync_complete'
|
||||
state['sync_progress'] = sync_state.get('progress', {})
|
||||
playlist_name = playlist_name_getter(state)
|
||||
add_activity_item("", "Sync Complete", f"{activity_subject} '{playlist_name}' synced successfully", "Now")
|
||||
elif sync_state.get('status') == 'error':
|
||||
state['phase'] = 'discovered'
|
||||
playlist_name = playlist_name_getter(state)
|
||||
add_activity_item("", "Sync Failed", f"{activity_subject} '{playlist_name}' sync failed", "Now")
|
||||
|
||||
return response, 200
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting {error_label} sync status: {e}")
|
||||
return {"error": str(e)}, 500
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ from core.discovery.endpoints import (
|
|||
convert_results_to_spotify_tracks,
|
||||
cancel_sync,
|
||||
delete_playlist_state,
|
||||
get_sync_status,
|
||||
playlist_name_strict as _pl_name_strict,
|
||||
playlist_name_safe as _pl_name_safe,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -259,3 +262,135 @@ def test_delete_exception_returns_500():
|
|||
assert "error" in body
|
||||
# state NOT deleted because the exception fired before del
|
||||
assert 'pl' in states
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# playlist-name accessors
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class _Obj:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
|
||||
def test_name_attr_or_unknown():
|
||||
from core.discovery.endpoints import playlist_name_attr_or_unknown as g
|
||||
assert g({'playlist': _Obj('My PL')}) == 'My PL'
|
||||
assert g({'playlist': {'name': 'dict'}}) == 'Unknown Playlist' # dict has no .name attr
|
||||
assert g({}) == 'Unknown Playlist'
|
||||
assert g({'playlist': None}) == 'Unknown Playlist'
|
||||
|
||||
|
||||
def test_name_strict():
|
||||
from core.discovery.endpoints import playlist_name_strict as g
|
||||
assert g({'playlist': {'name': 'X'}}) == 'X'
|
||||
import pytest
|
||||
with pytest.raises(KeyError):
|
||||
g({}) # strict -> raises, matching originals
|
||||
|
||||
|
||||
def test_name_safe():
|
||||
from core.discovery.endpoints import playlist_name_safe as g
|
||||
assert g({'playlist': {'name': 'X'}}) == 'X'
|
||||
assert g({}) == 'Unknown Playlist'
|
||||
assert g({'playlist': {}}) == 'Unknown Playlist'
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_sync_status
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _activity_recorder():
|
||||
calls = []
|
||||
|
||||
def add_activity_item(user, action, desc, when):
|
||||
calls.append((user, action, desc, when))
|
||||
|
||||
return calls, add_activity_item
|
||||
|
||||
|
||||
def _status_kwargs(infra, add_activity_item, *, not_found_message='nf',
|
||||
error_label='Tidal', activity_subject='Tidal playlist',
|
||||
name_getter=None):
|
||||
return dict(
|
||||
not_found_message=not_found_message, error_label=error_label,
|
||||
activity_subject=activity_subject,
|
||||
playlist_name_getter=name_getter or _pl_name_safe,
|
||||
add_activity_item=add_activity_item,
|
||||
sync_lock=infra['sync_lock'], sync_states=infra['sync_states'],
|
||||
)
|
||||
|
||||
|
||||
def test_status_not_found():
|
||||
infra = _cancel_infra()
|
||||
calls, add = _activity_recorder()
|
||||
body, code = get_sync_status({}, 'missing',
|
||||
**_status_kwargs(infra, add, not_found_message='Tidal playlist not found'))
|
||||
assert code == 404 and body == {"error": "Tidal playlist not found"}
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_status_no_sync_in_progress():
|
||||
infra = _cancel_infra()
|
||||
calls, add = _activity_recorder()
|
||||
states = {'pl': {'phase': 'discovered'}} # no sync_playlist_id
|
||||
body, code = get_sync_status(states, 'pl', **_status_kwargs(infra, add))
|
||||
assert code == 404 and body == {"error": "No sync in progress"}
|
||||
|
||||
|
||||
def test_status_running_returns_shape_without_mutation_or_activity():
|
||||
infra = _cancel_infra()
|
||||
infra['sync_states']['sp1'] = {"status": "running", "progress": {"n": 2}}
|
||||
calls, add = _activity_recorder()
|
||||
states = {'pl': {'phase': 'syncing', 'sync_playlist_id': 'sp1'}}
|
||||
body, code = get_sync_status(states, 'pl', **_status_kwargs(infra, add))
|
||||
assert code == 200
|
||||
assert body == {
|
||||
'phase': 'syncing', 'sync_status': 'running',
|
||||
'progress': {"n": 2}, 'complete': False, 'error': None,
|
||||
}
|
||||
assert states['pl']['phase'] == 'syncing' # unchanged
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_status_finished_sets_complete_and_posts_activity():
|
||||
infra = _cancel_infra()
|
||||
infra['sync_states']['sp1'] = {"status": "finished", "progress": {"done": 9}}
|
||||
calls, add = _activity_recorder()
|
||||
states = {'pl': {'phase': 'syncing', 'sync_playlist_id': 'sp1',
|
||||
'playlist': {'name': 'Mix'}}}
|
||||
body, code = get_sync_status(states, 'pl', **_status_kwargs(
|
||||
infra, add, activity_subject='Spotify Link playlist',
|
||||
name_getter=_pl_name_strict))
|
||||
assert code == 200
|
||||
assert body['complete'] is True
|
||||
assert states['pl']['phase'] == 'sync_complete'
|
||||
assert states['pl']['sync_progress'] == {"done": 9}
|
||||
assert calls == [("", "Sync Complete", "Spotify Link playlist 'Mix' synced successfully", "Now")]
|
||||
|
||||
|
||||
def test_status_error_reverts_and_posts_failed_activity():
|
||||
infra = _cancel_infra()
|
||||
infra['sync_states']['sp1'] = {"status": "error", "error": "boom"}
|
||||
calls, add = _activity_recorder()
|
||||
states = {'pl': {'phase': 'syncing', 'sync_playlist_id': 'sp1',
|
||||
'playlist': {'name': 'Mix'}}}
|
||||
body, code = get_sync_status(states, 'pl', **_status_kwargs(
|
||||
infra, add, activity_subject='YouTube playlist', name_getter=_pl_name_safe))
|
||||
assert code == 200
|
||||
assert body['error'] == "boom"
|
||||
assert states['pl']['phase'] == 'discovered'
|
||||
assert calls == [("", "Sync Failed", "YouTube playlist 'Mix' sync failed", "Now")]
|
||||
|
||||
|
||||
def test_status_strict_getter_missing_playlist_raises_500_after_partial_mutation():
|
||||
infra = _cancel_infra()
|
||||
infra['sync_states']['sp1'] = {"status": "finished", "progress": {}}
|
||||
calls, add = _activity_recorder()
|
||||
states = {'pl': {'phase': 'syncing', 'sync_playlist_id': 'sp1'}} # no 'playlist'
|
||||
body, code = get_sync_status(states, 'pl', **_status_kwargs(
|
||||
infra, add, error_label='Deezer', name_getter=_pl_name_strict))
|
||||
assert code == 500 and "error" in body
|
||||
# phase was set to sync_complete BEFORE the strict getter raised (1:1).
|
||||
assert states['pl']['phase'] == 'sync_complete'
|
||||
assert calls == [] # activity never posted
|
||||
|
|
|
|||
290
web_server.py
290
web_server.py
|
|
@ -21060,6 +21060,10 @@ from core.discovery.endpoints import (
|
|||
convert_results_to_spotify_tracks,
|
||||
cancel_sync as _cancel_sync_core,
|
||||
delete_playlist_state as _delete_playlist_state_core,
|
||||
get_sync_status as _get_sync_status_core,
|
||||
playlist_name_attr_or_unknown as _pl_name_attr_or_unknown,
|
||||
playlist_name_strict as _pl_name_strict,
|
||||
playlist_name_safe as _pl_name_safe,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -21083,6 +21087,18 @@ def _delete_source_playlist(states, key, label, not_found_message):
|
|||
return jsonify(body), code
|
||||
|
||||
|
||||
def _get_source_sync_status(states, key, not_found_message, error_label,
|
||||
activity_subject, name_getter):
|
||||
"""Thin glue for the per-source get_*_sync_status routes — wires the sync
|
||||
infra + add_activity_item into the lifted helper and jsonifies."""
|
||||
body, code = _get_sync_status_core(
|
||||
states, key, not_found_message=not_found_message, error_label=error_label,
|
||||
activity_subject=activity_subject, playlist_name_getter=name_getter,
|
||||
sync_lock=sync_lock, sync_states=sync_states, add_activity_item=add_activity_item,
|
||||
)
|
||||
return jsonify(body), code
|
||||
|
||||
|
||||
def _build_tidal_discovery_deps():
|
||||
"""Build the TidalDiscoveryDeps bundle from web_server.py globals on each call."""
|
||||
return _discovery_tidal.TidalDiscoveryDeps(
|
||||
|
|
@ -21174,48 +21190,7 @@ def start_tidal_sync(playlist_id):
|
|||
@app.route('/api/tidal/sync/status/<playlist_id>', methods=['GET'])
|
||||
def get_tidal_sync_status(playlist_id):
|
||||
"""Get sync status 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 not sync_playlist_id:
|
||||
return jsonify({"error": "No sync in progress"}), 404
|
||||
|
||||
# Get sync status from existing sync infrastructure
|
||||
with sync_lock:
|
||||
sync_state = sync_states.get(sync_playlist_id, {})
|
||||
|
||||
response = {
|
||||
'phase': state['phase'],
|
||||
'sync_status': sync_state.get('status', 'unknown'),
|
||||
'progress': sync_state.get('progress', {}),
|
||||
'complete': sync_state.get('status') == 'finished',
|
||||
'error': sync_state.get('error')
|
||||
}
|
||||
|
||||
# Update Tidal state if sync completed
|
||||
if sync_state.get('status') == 'finished':
|
||||
state['phase'] = 'sync_complete'
|
||||
state['sync_progress'] = sync_state.get('progress', {})
|
||||
# Add activity for sync completion
|
||||
playlist = state.get('playlist')
|
||||
playlist_name = playlist.name if playlist and hasattr(playlist, 'name') else 'Unknown Playlist'
|
||||
add_activity_item("", "Sync Complete", f"Tidal playlist '{playlist_name}' synced successfully", "Now")
|
||||
elif sync_state.get('status') == 'error':
|
||||
state['phase'] = 'discovered' # Revert on error
|
||||
playlist = state.get('playlist')
|
||||
playlist_name = playlist.name if playlist and hasattr(playlist, 'name') else 'Unknown Playlist'
|
||||
add_activity_item("", "Sync Failed", f"Tidal playlist '{playlist_name}' sync failed", "Now")
|
||||
|
||||
return jsonify(response)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting Tidal sync status: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _get_source_sync_status(tidal_discovery_states, playlist_id, "Tidal playlist not found", "Tidal", "Tidal playlist", _pl_name_attr_or_unknown)
|
||||
|
||||
@app.route('/api/tidal/sync/cancel/<playlist_id>', methods=['POST'])
|
||||
def cancel_tidal_sync(playlist_id):
|
||||
|
|
@ -21812,45 +21787,7 @@ def start_deezer_sync(playlist_id):
|
|||
@app.route('/api/deezer/sync/status/<playlist_id>', methods=['GET'])
|
||||
def get_deezer_sync_status(playlist_id):
|
||||
"""Get sync status 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 not sync_playlist_id:
|
||||
return jsonify({"error": "No sync in progress"}), 404
|
||||
|
||||
# Get sync status from existing sync infrastructure
|
||||
with sync_lock:
|
||||
sync_state = sync_states.get(sync_playlist_id, {})
|
||||
|
||||
response = {
|
||||
'phase': state['phase'],
|
||||
'sync_status': sync_state.get('status', 'unknown'),
|
||||
'progress': sync_state.get('progress', {}),
|
||||
'complete': sync_state.get('status') == 'finished',
|
||||
'error': sync_state.get('error')
|
||||
}
|
||||
|
||||
# Update Deezer state if sync completed
|
||||
if sync_state.get('status') == 'finished':
|
||||
state['phase'] = 'sync_complete'
|
||||
state['sync_progress'] = sync_state.get('progress', {})
|
||||
playlist_name = state['playlist']['name']
|
||||
add_activity_item("", "Sync Complete", f"Deezer playlist '{playlist_name}' synced successfully", "Now")
|
||||
elif sync_state.get('status') == 'error':
|
||||
state['phase'] = 'discovered' # Revert on error
|
||||
playlist_name = state['playlist']['name']
|
||||
add_activity_item("", "Sync Failed", f"Deezer playlist '{playlist_name}' sync failed", "Now")
|
||||
|
||||
return jsonify(response)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting Deezer sync status: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _get_source_sync_status(deezer_discovery_states, playlist_id, "Deezer playlist not found", "Deezer", "Deezer playlist", _pl_name_strict)
|
||||
|
||||
@app.route('/api/deezer/sync/cancel/<playlist_id>', methods=['POST'])
|
||||
def cancel_deezer_sync(playlist_id):
|
||||
|
|
@ -22395,43 +22332,7 @@ def start_qobuz_sync(playlist_id):
|
|||
@app.route('/api/qobuz/sync/status/<playlist_id>', methods=['GET'])
|
||||
def get_qobuz_sync_status(playlist_id):
|
||||
"""Get sync status 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 not sync_playlist_id:
|
||||
return jsonify({"error": "No sync in progress"}), 404
|
||||
|
||||
with sync_lock:
|
||||
sync_state = sync_states.get(sync_playlist_id, {})
|
||||
|
||||
response = {
|
||||
'phase': state['phase'],
|
||||
'sync_status': sync_state.get('status', 'unknown'),
|
||||
'progress': sync_state.get('progress', {}),
|
||||
'complete': sync_state.get('status') == 'finished',
|
||||
'error': sync_state.get('error')
|
||||
}
|
||||
|
||||
if sync_state.get('status') == 'finished':
|
||||
state['phase'] = 'sync_complete'
|
||||
state['sync_progress'] = sync_state.get('progress', {})
|
||||
playlist_name = state['playlist']['name']
|
||||
add_activity_item("", "Sync Complete", f"Qobuz playlist '{playlist_name}' synced successfully", "Now")
|
||||
elif sync_state.get('status') == 'error':
|
||||
state['phase'] = 'discovered'
|
||||
playlist_name = state['playlist']['name']
|
||||
add_activity_item("", "Sync Failed", f"Qobuz playlist '{playlist_name}' sync failed", "Now")
|
||||
|
||||
return jsonify(response)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting Qobuz sync status: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _get_source_sync_status(qobuz_discovery_states, playlist_id, "Qobuz playlist not found", "Qobuz", "Qobuz playlist", _pl_name_strict)
|
||||
|
||||
|
||||
@app.route('/api/qobuz/sync/cancel/<playlist_id>', methods=['POST'])
|
||||
|
|
@ -23271,45 +23172,7 @@ def start_spotify_public_sync(url_hash):
|
|||
@app.route('/api/spotify-public/sync/status/<url_hash>', methods=['GET'])
|
||||
def get_spotify_public_sync_status(url_hash):
|
||||
"""Get sync status 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 not sync_playlist_id:
|
||||
return jsonify({"error": "No sync in progress"}), 404
|
||||
|
||||
# Get sync status from existing sync infrastructure
|
||||
with sync_lock:
|
||||
sync_state = sync_states.get(sync_playlist_id, {})
|
||||
|
||||
response = {
|
||||
'phase': state['phase'],
|
||||
'sync_status': sync_state.get('status', 'unknown'),
|
||||
'progress': sync_state.get('progress', {}),
|
||||
'complete': sync_state.get('status') == 'finished',
|
||||
'error': sync_state.get('error')
|
||||
}
|
||||
|
||||
# Update Spotify Public state if sync completed
|
||||
if sync_state.get('status') == 'finished':
|
||||
state['phase'] = 'sync_complete'
|
||||
state['sync_progress'] = sync_state.get('progress', {})
|
||||
playlist_name = state['playlist']['name']
|
||||
add_activity_item("", "Sync Complete", f"Spotify Link playlist '{playlist_name}' synced successfully", "Now")
|
||||
elif sync_state.get('status') == 'error':
|
||||
state['phase'] = 'discovered' # Revert on error
|
||||
playlist_name = state['playlist']['name']
|
||||
add_activity_item("", "Sync Failed", f"Spotify Link playlist '{playlist_name}' sync failed", "Now")
|
||||
|
||||
return jsonify(response)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting Spotify Public sync status: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _get_source_sync_status(spotify_public_discovery_states, url_hash, "Spotify Public playlist not found", "Spotify Public", "Spotify Link playlist", _pl_name_strict)
|
||||
|
||||
@app.route('/api/spotify-public/sync/cancel/<url_hash>', methods=['POST'])
|
||||
def cancel_spotify_public_sync(url_hash):
|
||||
|
|
@ -23711,35 +23574,7 @@ def start_itunes_link_sync(url_hash):
|
|||
|
||||
@app.route('/api/itunes-link/sync/status/<url_hash>', methods=['GET'])
|
||||
def get_itunes_link_sync_status(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 not sync_playlist_id:
|
||||
return jsonify({"error": "No sync in progress"}), 404
|
||||
|
||||
with sync_lock:
|
||||
sync_state = sync_states.get(sync_playlist_id, {})
|
||||
response = {
|
||||
'phase': state['phase'],
|
||||
'sync_status': sync_state.get('status', 'unknown'),
|
||||
'progress': sync_state.get('progress', {}),
|
||||
'complete': sync_state.get('status') == 'finished',
|
||||
'error': sync_state.get('error')
|
||||
}
|
||||
if sync_state.get('status') == 'finished':
|
||||
state['phase'] = 'sync_complete'
|
||||
state['sync_progress'] = sync_state.get('progress', {})
|
||||
add_activity_item("", "Sync Complete", f"iTunes Link '{state['playlist']['name']}' synced successfully", "Now")
|
||||
elif sync_state.get('status') == 'error':
|
||||
state['phase'] = 'discovered'
|
||||
add_activity_item("", "Sync Failed", f"iTunes Link '{state['playlist']['name']}' sync failed", "Now")
|
||||
return jsonify(response)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting iTunes Link sync status: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _get_source_sync_status(itunes_link_discovery_states, url_hash, "iTunes Link not found", "iTunes Link", "iTunes Link", _pl_name_strict)
|
||||
|
||||
|
||||
@app.route('/api/itunes-link/sync/cancel/<url_hash>', methods=['POST'])
|
||||
|
|
@ -24348,46 +24183,7 @@ def start_youtube_sync(url_hash):
|
|||
@app.route('/api/youtube/sync/status/<url_hash>', methods=['GET'])
|
||||
def get_youtube_sync_status(url_hash):
|
||||
"""Get sync status 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 not sync_playlist_id:
|
||||
return jsonify({"error": "No sync in progress"}), 404
|
||||
|
||||
# Get sync status from existing sync infrastructure
|
||||
with sync_lock:
|
||||
sync_state = sync_states.get(sync_playlist_id, {})
|
||||
|
||||
response = {
|
||||
'phase': state['phase'],
|
||||
'sync_status': sync_state.get('status', 'unknown'),
|
||||
'progress': sync_state.get('progress', {}),
|
||||
'complete': sync_state.get('status') == 'finished',
|
||||
'error': sync_state.get('error')
|
||||
}
|
||||
|
||||
# Update YouTube state if sync completed
|
||||
if sync_state.get('status') == 'finished':
|
||||
state['phase'] = 'sync_complete'
|
||||
state['sync_progress'] = sync_state.get('progress', {})
|
||||
# Add activity for sync completion
|
||||
playlist_name = state.get('playlist', {}).get('name', 'Unknown Playlist')
|
||||
add_activity_item("", "Sync Complete", f"YouTube playlist '{playlist_name}' synced successfully", "Now")
|
||||
elif sync_state.get('status') == 'error':
|
||||
state['phase'] = 'discovered' # Revert on error
|
||||
playlist_name = state.get('playlist', {}).get('name', 'Unknown Playlist')
|
||||
add_activity_item("", "Sync Failed", f"YouTube playlist '{playlist_name}' sync failed", "Now")
|
||||
|
||||
return jsonify(response)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting YouTube sync status: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _get_source_sync_status(youtube_playlist_states, url_hash, "YouTube playlist not found", "YouTube", "YouTube playlist", _pl_name_safe)
|
||||
|
||||
@app.route('/api/youtube/sync/cancel/<url_hash>', methods=['POST'])
|
||||
def cancel_youtube_sync(url_hash):
|
||||
|
|
@ -30456,47 +30252,7 @@ def start_listenbrainz_sync(playlist_mbid):
|
|||
@app.route('/api/listenbrainz/sync/status/<playlist_mbid>', methods=['GET'])
|
||||
def get_listenbrainz_sync_status(playlist_mbid):
|
||||
"""Get sync status 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 not sync_playlist_id:
|
||||
return jsonify({"error": "No sync in progress"}), 404
|
||||
|
||||
# Get sync status from existing sync infrastructure
|
||||
with sync_lock:
|
||||
sync_state = sync_states.get(sync_playlist_id, {})
|
||||
|
||||
response = {
|
||||
'phase': state['phase'],
|
||||
'sync_status': sync_state.get('status', 'unknown'),
|
||||
'progress': sync_state.get('progress', {}),
|
||||
'complete': sync_state.get('status') == 'finished',
|
||||
'error': sync_state.get('error')
|
||||
}
|
||||
|
||||
# Update ListenBrainz state if sync completed
|
||||
if sync_state.get('status') == 'finished':
|
||||
state['phase'] = 'sync_complete'
|
||||
state['sync_progress'] = sync_state.get('progress', {})
|
||||
# Add activity for sync completion
|
||||
playlist_name = state.get('playlist', {}).get('name', 'Unknown Playlist')
|
||||
add_activity_item("", "Sync Complete", f"ListenBrainz playlist '{playlist_name}' synced successfully", "Now")
|
||||
elif sync_state.get('status') == 'error':
|
||||
state['phase'] = 'discovered' # Revert on error
|
||||
playlist_name = state.get('playlist', {}).get('name', 'Unknown Playlist')
|
||||
add_activity_item("", "Sync Failed", f"ListenBrainz playlist '{playlist_name}' sync failed", "Now")
|
||||
|
||||
return jsonify(response)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting ListenBrainz sync status: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return _get_source_sync_status(listenbrainz_playlist_states, _lb_state_key(playlist_mbid), "ListenBrainz playlist not found", "ListenBrainz", "ListenBrainz playlist", _pl_name_safe)
|
||||
|
||||
@app.route('/api/listenbrainz/sync/cancel/<playlist_mbid>', methods=['POST'])
|
||||
def cancel_listenbrainz_sync(playlist_mbid):
|
||||
|
|
|
|||
Loading…
Reference in a new issue