Add smart-skip to sync playlist node for efficient scheduled syncing
Sync now compares current discovered track count against last sync's matched count. Skips when all discovered tracks are already on the server. Re-syncs when wishlist may have downloaded missing tracks since last sync. Saves matched/discovered/total counts to sync status after each run. Enables putting sync on a schedule without wasting resources — it skips instantly when nothing changed and only runs when there's something new.
This commit is contained in:
parent
a7e60f2aaf
commit
4a308483ab
1 changed files with 53 additions and 7 deletions
|
|
@ -713,7 +713,8 @@ def _register_automation_handlers():
|
|||
|
||||
def _auto_sync_playlist(config):
|
||||
"""Sync a mirrored playlist to media server.
|
||||
Uses discovered metadata when available, skips undiscovered tracks."""
|
||||
Uses discovered metadata when available, skips undiscovered tracks.
|
||||
When triggered on a schedule, skips if nothing changed since last sync."""
|
||||
auto_id = config.get('_automation_id')
|
||||
playlist_id = config.get('playlist_id')
|
||||
if not playlist_id:
|
||||
|
|
@ -728,6 +729,44 @@ def _register_automation_handlers():
|
|||
if not tracks:
|
||||
return {'status': 'error', 'reason': 'No tracks in playlist'}
|
||||
|
||||
# Count currently discovered tracks for smart-skip check
|
||||
current_discovered = 0
|
||||
for t in tracks:
|
||||
extra = {}
|
||||
if t.get('extra_data'):
|
||||
try:
|
||||
extra = json.loads(t['extra_data']) if isinstance(t['extra_data'], str) else t['extra_data']
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
if extra.get('discovered') and extra.get('matched_data'):
|
||||
current_discovered += 1
|
||||
|
||||
# Smart skip: only re-sync if something could have changed
|
||||
# Skip when: same discovered count, same total, AND last sync already matched all discovered tracks
|
||||
# Re-sync when: new tracks discovered, tracks added/removed, or last sync had unmatched tracks
|
||||
# (unmatched = discovered but not on server — wishlist may have downloaded them since)
|
||||
sync_id_key = f"auto_mirror_{playlist_id}"
|
||||
try:
|
||||
sync_statuses = _load_sync_status_file()
|
||||
last_status = sync_statuses.get(sync_id_key, {})
|
||||
last_matched = last_status.get('matched_tracks', -1)
|
||||
last_discovered = last_status.get('discovered_tracks', -1)
|
||||
last_total = last_status.get('total_tracks', -1)
|
||||
|
||||
if (last_discovered == current_discovered and
|
||||
last_total == len(tracks) and
|
||||
last_matched >= current_discovered):
|
||||
# All discovered tracks were already matched on server last time — nothing to do
|
||||
_update_automation_progress(auto_id,
|
||||
log_line=f'All {current_discovered} tracks already synced — skipping',
|
||||
log_type='skip')
|
||||
return {
|
||||
'status': 'skipped',
|
||||
'reason': f'All {current_discovered} tracks already synced',
|
||||
}
|
||||
except Exception:
|
||||
pass # If we can't read last status, just run the sync
|
||||
|
||||
# Convert mirrored tracks to format expected by _run_sync_task
|
||||
# Use discovered metadata when available, skip undiscovered tracks
|
||||
tracks_json = []
|
||||
|
|
@ -23985,21 +24024,26 @@ def _save_sync_status_file(sync_statuses):
|
|||
except Exception as e:
|
||||
print(f"❌ Error saving sync status: {e}")
|
||||
|
||||
def _update_and_save_sync_status(playlist_id, playlist_name, playlist_owner, snapshot_id):
|
||||
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:
|
||||
# Load existing sync statuses
|
||||
sync_statuses = _load_sync_status_file()
|
||||
|
||||
|
||||
# Update this playlist's sync status
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
sync_statuses[playlist_id] = {
|
||||
status = {
|
||||
'name': playlist_name,
|
||||
'owner': playlist_owner,
|
||||
'snapshot_id': snapshot_id,
|
||||
'last_synced': now.isoformat()
|
||||
}
|
||||
# Store match counts for smart-skip on scheduled syncs
|
||||
for key in ('matched_tracks', 'total_tracks', 'discovered_tracks'):
|
||||
if key in kwargs:
|
||||
status[key] = kwargs[key]
|
||||
sync_statuses[playlist_id] = status
|
||||
|
||||
# Save to file
|
||||
_save_sync_status_file(sync_statuses)
|
||||
|
|
@ -29236,10 +29280,12 @@ def _run_sync_task(playlist_id, playlist_name, tracks_json, automation_id=None):
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# Save sync status to storage/sync_status.json (same as GUI)
|
||||
# Handle snapshot_id safely - may not exist in all playlist objects
|
||||
# Save sync status with match counts for smart-skip on next scheduled sync
|
||||
snapshot_id = getattr(playlist, 'snapshot_id', None)
|
||||
_update_and_save_sync_status(playlist_id, playlist_name, playlist.owner, snapshot_id)
|
||||
_update_and_save_sync_status(playlist_id, playlist_name, playlist.owner, snapshot_id,
|
||||
matched_tracks=getattr(result, 'matched_tracks', 0),
|
||||
total_tracks=getattr(result, 'total_tracks', 0),
|
||||
discovered_tracks=len(tracks_json))
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ SYNC FAILED for {playlist_id}: {e}")
|
||||
|
|
|
|||
Loading…
Reference in a new issue