PR #780 follow-ups: snapshot-based stale check + submit guard + dead code
- Stale-cache check (playlistTrackCacheIsStale) compared raw track_count to the filtered/cached track list, so any playlist with local or unavailable tracks always looked 'stale' and refetched + re-mirrored on every modal open. Now it compares the upstream snapshot_id (stored at cache time in the shared fetch choke point), and returns not-stale when no snapshot is available — explicit invalidation on refresh still handles real changes. - organize_download: guard executor.submit so a refused job cleans up the batch instead of stranding it in 'analysis' (holding a limited analysis slot). - Removed the dead, deprecated, unused mirrorSpotifyPlaylistTracks.
This commit is contained in:
parent
a977d28144
commit
c0c4528a28
3 changed files with 33 additions and 14 deletions
|
|
@ -152,12 +152,20 @@ def run_playlist_organize_download(
|
|||
except Exception as hist_err:
|
||||
logger.debug("organize download sync history: %s", hist_err)
|
||||
|
||||
deps.missing_download_executor.submit(
|
||||
run_full_missing_tracks_process,
|
||||
batch_id,
|
||||
playlist_id,
|
||||
tracks_json,
|
||||
)
|
||||
try:
|
||||
deps.missing_download_executor.submit(
|
||||
run_full_missing_tracks_process,
|
||||
batch_id,
|
||||
playlist_id,
|
||||
tracks_json,
|
||||
)
|
||||
except Exception as submit_err:
|
||||
# Don't leave the batch stranded in 'analysis' holding one of the limited
|
||||
# analysis slots if the executor refuses the job.
|
||||
logger.error("[Organize Download] Failed to submit batch %s: %s", batch_id, submit_err)
|
||||
with tasks_lock:
|
||||
download_batches.pop(batch_id, None)
|
||||
return {'status': 'error', 'reason': f'submit failed: {submit_err}'}
|
||||
logger.info(
|
||||
"[Organize Download] Started batch %s for mirrored playlist %s (%s tracks)",
|
||||
batch_id,
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ let _lastWatchlistScanStatus = null;
|
|||
let _lastMediaScanStatus = null;
|
||||
let _lastWishlistStats = null;
|
||||
let playlistTrackCache = {}; // Key: playlist_id, Value: tracks array
|
||||
let playlistTrackSnapshotCache = {}; // Key: playlist_id, Value: upstream snapshot_id at cache time
|
||||
let spotifyPlaylistsLoaded = false;
|
||||
let activeDownloadProcesses = {};
|
||||
let sequentialSyncManager = null;
|
||||
|
|
|
|||
|
|
@ -1097,10 +1097,20 @@ function playlistTrackCacheIsStale(playlistId, playlist) {
|
|||
if (!cached) {
|
||||
return false;
|
||||
}
|
||||
const expected = playlist?.track_count;
|
||||
if (expected != null && Number(expected) !== cached.length) {
|
||||
return true;
|
||||
// Compare the upstream snapshot id (changes whenever the playlist's contents
|
||||
// change). This avoids the false-positive from comparing raw track_count to
|
||||
// the cached/filtered track list — playlists with local or unavailable
|
||||
// tracks report a higher track_count than the id-bearing tracks we cache,
|
||||
// which made every open look "stale" and refetch + re-mirror needlessly.
|
||||
const currentSnap = playlist?.snapshot_id;
|
||||
const cachedSnap = (typeof playlistTrackSnapshotCache !== 'undefined')
|
||||
? playlistTrackSnapshotCache[playlistId]
|
||||
: undefined;
|
||||
if (currentSnap && cachedSnap != null && cachedSnap !== '') {
|
||||
return String(currentSnap) !== String(cachedSnap);
|
||||
}
|
||||
// No reliable snapshot to compare (e.g. sources without snapshots) — don't
|
||||
// force a refetch here; explicit invalidation on refresh handles real changes.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1144,6 +1154,7 @@ function isPlaylistDownloadProcessStale(playlistId, playlistMeta) {
|
|||
function invalidatePlaylistTrackCache(playlistId = null) {
|
||||
if (playlistId) {
|
||||
delete playlistTrackCache[playlistId];
|
||||
if (typeof playlistTrackSnapshotCache !== 'undefined') delete playlistTrackSnapshotCache[playlistId];
|
||||
if (currentModalPlaylistId === playlistId) {
|
||||
currentPlaylistTracks = [];
|
||||
}
|
||||
|
|
@ -1151,6 +1162,7 @@ function invalidatePlaylistTrackCache(playlistId = null) {
|
|||
return;
|
||||
}
|
||||
playlistTrackCache = {};
|
||||
if (typeof playlistTrackSnapshotCache !== 'undefined') playlistTrackSnapshotCache = {};
|
||||
currentPlaylistTracks = [];
|
||||
const ids = new Set();
|
||||
if (typeof spotifyPlaylists !== 'undefined' && Array.isArray(spotifyPlaylists)) {
|
||||
|
|
@ -1195,11 +1207,6 @@ function mirrorPlaylistTracksForSource(source, sourcePlaylistId, fullPlaylist) {
|
|||
});
|
||||
}
|
||||
|
||||
/** @deprecated Use mirrorPlaylistTracksForSource */
|
||||
function mirrorSpotifyPlaylistTracks(playlistId, fullPlaylist) {
|
||||
mirrorPlaylistTracksForSource('spotify', playlistId, fullPlaylist);
|
||||
}
|
||||
|
||||
async function fetchAndCachePlaylistTracks(cacheKey, fetchUrl, mirrorSource, mirrorSourceId) {
|
||||
const response = await fetch(fetchUrl);
|
||||
const fullPlaylist = await response.json();
|
||||
|
|
@ -1207,6 +1214,9 @@ async function fetchAndCachePlaylistTracks(cacheKey, fetchUrl, mirrorSource, mir
|
|||
throw new Error(fullPlaylist.error);
|
||||
}
|
||||
playlistTrackCache[cacheKey] = fullPlaylist.tracks;
|
||||
// Remember the upstream snapshot so staleness can compare against a stable
|
||||
// signal instead of raw track_count vs the filtered track list.
|
||||
playlistTrackSnapshotCache[cacheKey] = fullPlaylist.snapshot_id || '';
|
||||
mirrorPlaylistTracksForSource(mirrorSource, mirrorSourceId, fullPlaylist);
|
||||
return fullPlaylist;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue