Fix playlist sync status labels
This commit is contained in:
parent
729a06c6d7
commit
ad657f02a8
3 changed files with 119 additions and 7 deletions
65
tests/test_playlist_sync_status.py
Normal file
65
tests/test_playlist_sync_status.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from web_server import (
|
||||
_format_playlist_sync_status,
|
||||
_resolve_spotify_playlist_sync_status,
|
||||
)
|
||||
|
||||
|
||||
class _StubDatabase:
|
||||
def __init__(self, mirrored):
|
||||
self._mirrored = mirrored
|
||||
|
||||
def get_mirrored_playlist_by_source(self, source, source_playlist_id, profile_id):
|
||||
assert source == 'spotify'
|
||||
assert source_playlist_id == 'spotify-playlist-1'
|
||||
assert profile_id == 7
|
||||
return self._mirrored
|
||||
|
||||
|
||||
def _status(minutes_ago=0, **overrides):
|
||||
timestamp = (datetime(2026, 6, 25, 12, 0, 0) - timedelta(minutes=minutes_ago)).isoformat()
|
||||
return {'last_synced': timestamp, **overrides}
|
||||
|
||||
|
||||
def test_resolve_spotify_playlist_sync_status_uses_mirrored_auto_sync_status():
|
||||
sync_statuses = {
|
||||
'auto_mirror_42': _status(matched_tracks=12),
|
||||
}
|
||||
|
||||
status = _resolve_spotify_playlist_sync_status(
|
||||
'spotify-playlist-1',
|
||||
sync_statuses,
|
||||
database=_StubDatabase({'id': 42}),
|
||||
profile_id=7,
|
||||
)
|
||||
|
||||
assert status['matched_tracks'] == 12
|
||||
|
||||
|
||||
def test_resolve_spotify_playlist_sync_status_prefers_newest_status():
|
||||
sync_statuses = {
|
||||
'spotify-playlist-1': _status(minutes_ago=20, matched_tracks=1),
|
||||
'auto_mirror_42': _status(minutes_ago=5, matched_tracks=2),
|
||||
}
|
||||
|
||||
status = _resolve_spotify_playlist_sync_status(
|
||||
'spotify-playlist-1',
|
||||
sync_statuses,
|
||||
database=_StubDatabase({'id': 42}),
|
||||
profile_id=7,
|
||||
)
|
||||
|
||||
assert status['matched_tracks'] == 2
|
||||
|
||||
|
||||
def test_format_playlist_sync_status_treats_missing_snapshot_as_synced():
|
||||
status = _status()
|
||||
|
||||
assert _format_playlist_sync_status(status, 'current-snapshot') == 'Synced: Jun 25, 12:00'
|
||||
|
||||
|
||||
def test_format_playlist_sync_status_marks_snapshot_mismatch_as_last_sync():
|
||||
status = _status(snapshot_id='old-snapshot')
|
||||
|
||||
assert _format_playlist_sync_status(status, 'current-snapshot') == 'Last Sync: Jun 25, 12:00'
|
||||
|
|
@ -20765,6 +20765,56 @@ def _save_sync_status_file(sync_statuses):
|
|||
except Exception as e:
|
||||
logger.error(f"Error saving sync status: {e}")
|
||||
|
||||
def _sync_status_timestamp(status_info):
|
||||
"""Return comparable timestamp for a persisted sync-status record."""
|
||||
if not status_info or 'last_synced' not in status_info:
|
||||
return None
|
||||
try:
|
||||
return datetime.fromisoformat(status_info['last_synced'])
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
def _latest_sync_status(*status_infos):
|
||||
"""Pick the newest non-empty sync-status record."""
|
||||
candidates = [s for s in status_infos if s]
|
||||
if not candidates:
|
||||
return {}
|
||||
return max(candidates, key=lambda s: _sync_status_timestamp(s) or datetime.min)
|
||||
|
||||
def _mirrored_spotify_sync_status(playlist_id, sync_statuses, *, database=None, profile_id=None):
|
||||
"""Return auto-sync status for a Spotify playlist mirrored into SoulSync."""
|
||||
try:
|
||||
db = database or get_database()
|
||||
profile = profile_id if profile_id is not None else get_current_profile_id()
|
||||
mirrored = db.get_mirrored_playlist_by_source('spotify', str(playlist_id), profile)
|
||||
if not mirrored:
|
||||
return {}
|
||||
return sync_statuses.get(f"auto_mirror_{mirrored.get('id')}", {})
|
||||
except Exception as e:
|
||||
logger.debug("Spotify mirrored sync-status lookup failed for %s: %s", playlist_id, e)
|
||||
return {}
|
||||
|
||||
def _resolve_spotify_playlist_sync_status(playlist_id, sync_statuses, *, database=None, profile_id=None):
|
||||
"""Resolve direct or mirrored sync status for a Spotify playlist card."""
|
||||
direct_status = sync_statuses.get(playlist_id, {})
|
||||
mirrored_status = _mirrored_spotify_sync_status(
|
||||
playlist_id,
|
||||
sync_statuses,
|
||||
database=database,
|
||||
profile_id=profile_id,
|
||||
)
|
||||
return _latest_sync_status(direct_status, mirrored_status)
|
||||
|
||||
def _format_playlist_sync_status(status_info, playlist_snapshot):
|
||||
"""Build user-facing sync-status text from persisted status + snapshot."""
|
||||
if 'last_synced' not in status_info:
|
||||
return "Never Synced"
|
||||
last_sync_time = datetime.fromisoformat(status_info['last_synced']).strftime('%b %d, %H:%M')
|
||||
stored_snapshot = status_info.get('snapshot_id')
|
||||
if stored_snapshot and playlist_snapshot and playlist_snapshot != stored_snapshot:
|
||||
return f"Last Sync: {last_sync_time}"
|
||||
return f"Synced: {last_sync_time}"
|
||||
|
||||
def _update_and_save_sync_status(playlist_id, playlist_name, playlist_owner, snapshot_id, **kwargs):
|
||||
"""Updates the sync status for a given playlist and saves to file (same logic as GUI)."""
|
||||
try:
|
||||
|
|
@ -20807,16 +20857,14 @@ def get_spotify_playlists():
|
|||
|
||||
# Add regular playlists first
|
||||
for p in playlists:
|
||||
status_info = sync_statuses.get(p.id, {})
|
||||
sync_status = "Never Synced"
|
||||
status_info = _resolve_spotify_playlist_sync_status(p.id, sync_statuses)
|
||||
# Handle snapshot_id safely - may not exist in core Playlist class
|
||||
playlist_snapshot = getattr(p, 'snapshot_id', '')
|
||||
sync_status = _format_playlist_sync_status(status_info, playlist_snapshot)
|
||||
|
||||
if 'last_synced' in status_info:
|
||||
stored_snapshot = status_info.get('snapshot_id')
|
||||
last_sync_time = datetime.fromisoformat(status_info['last_synced']).strftime('%b %d, %H:%M')
|
||||
if playlist_snapshot != stored_snapshot:
|
||||
sync_status = f"Last Sync: {last_sync_time}"
|
||||
if stored_snapshot and playlist_snapshot and playlist_snapshot != stored_snapshot:
|
||||
logger.info(
|
||||
"Playlist sync status: name=%s id=%s snapshot=%r stored_snapshot=%r result=Needs Sync display=%s",
|
||||
p.name,
|
||||
|
|
@ -20826,7 +20874,6 @@ def get_spotify_playlists():
|
|||
sync_status,
|
||||
)
|
||||
else:
|
||||
sync_status = f"Synced: {last_sync_time}"
|
||||
logger.info(
|
||||
"Playlist sync status: name=%s id=%s snapshot=%r stored_snapshot=%r result=Synced display=%s",
|
||||
p.name,
|
||||
|
|
|
|||
|
|
@ -1639,7 +1639,7 @@ function renderSpotifyPlaylists() {
|
|||
container.innerHTML = spotifyPlaylists.map(p => {
|
||||
let statusClass = 'status-never-synced';
|
||||
if (p.sync_status.startsWith('Synced')) statusClass = 'status-synced';
|
||||
if (p.sync_status === 'Needs Sync') statusClass = 'status-needs-sync';
|
||||
if (p.sync_status === 'Needs Sync' || p.sync_status.startsWith('Last Sync')) statusClass = 'status-needs-sync';
|
||||
|
||||
// This HTML structure creates the interactive playlist cards
|
||||
return `
|
||||
|
|
|
|||
Loading…
Reference in a new issue