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:
|
except Exception as hist_err:
|
||||||
logger.debug("organize download sync history: %s", hist_err)
|
logger.debug("organize download sync history: %s", hist_err)
|
||||||
|
|
||||||
deps.missing_download_executor.submit(
|
try:
|
||||||
run_full_missing_tracks_process,
|
deps.missing_download_executor.submit(
|
||||||
batch_id,
|
run_full_missing_tracks_process,
|
||||||
playlist_id,
|
batch_id,
|
||||||
tracks_json,
|
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(
|
logger.info(
|
||||||
"[Organize Download] Started batch %s for mirrored playlist %s (%s tracks)",
|
"[Organize Download] Started batch %s for mirrored playlist %s (%s tracks)",
|
||||||
batch_id,
|
batch_id,
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ let _lastWatchlistScanStatus = null;
|
||||||
let _lastMediaScanStatus = null;
|
let _lastMediaScanStatus = null;
|
||||||
let _lastWishlistStats = null;
|
let _lastWishlistStats = null;
|
||||||
let playlistTrackCache = {}; // Key: playlist_id, Value: tracks array
|
let playlistTrackCache = {}; // Key: playlist_id, Value: tracks array
|
||||||
|
let playlistTrackSnapshotCache = {}; // Key: playlist_id, Value: upstream snapshot_id at cache time
|
||||||
let spotifyPlaylistsLoaded = false;
|
let spotifyPlaylistsLoaded = false;
|
||||||
let activeDownloadProcesses = {};
|
let activeDownloadProcesses = {};
|
||||||
let sequentialSyncManager = null;
|
let sequentialSyncManager = null;
|
||||||
|
|
|
||||||
|
|
@ -1097,10 +1097,20 @@ function playlistTrackCacheIsStale(playlistId, playlist) {
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const expected = playlist?.track_count;
|
// Compare the upstream snapshot id (changes whenever the playlist's contents
|
||||||
if (expected != null && Number(expected) !== cached.length) {
|
// change). This avoids the false-positive from comparing raw track_count to
|
||||||
return true;
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1144,6 +1154,7 @@ function isPlaylistDownloadProcessStale(playlistId, playlistMeta) {
|
||||||
function invalidatePlaylistTrackCache(playlistId = null) {
|
function invalidatePlaylistTrackCache(playlistId = null) {
|
||||||
if (playlistId) {
|
if (playlistId) {
|
||||||
delete playlistTrackCache[playlistId];
|
delete playlistTrackCache[playlistId];
|
||||||
|
if (typeof playlistTrackSnapshotCache !== 'undefined') delete playlistTrackSnapshotCache[playlistId];
|
||||||
if (currentModalPlaylistId === playlistId) {
|
if (currentModalPlaylistId === playlistId) {
|
||||||
currentPlaylistTracks = [];
|
currentPlaylistTracks = [];
|
||||||
}
|
}
|
||||||
|
|
@ -1151,6 +1162,7 @@ function invalidatePlaylistTrackCache(playlistId = null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
playlistTrackCache = {};
|
playlistTrackCache = {};
|
||||||
|
if (typeof playlistTrackSnapshotCache !== 'undefined') playlistTrackSnapshotCache = {};
|
||||||
currentPlaylistTracks = [];
|
currentPlaylistTracks = [];
|
||||||
const ids = new Set();
|
const ids = new Set();
|
||||||
if (typeof spotifyPlaylists !== 'undefined' && Array.isArray(spotifyPlaylists)) {
|
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) {
|
async function fetchAndCachePlaylistTracks(cacheKey, fetchUrl, mirrorSource, mirrorSourceId) {
|
||||||
const response = await fetch(fetchUrl);
|
const response = await fetch(fetchUrl);
|
||||||
const fullPlaylist = await response.json();
|
const fullPlaylist = await response.json();
|
||||||
|
|
@ -1207,6 +1214,9 @@ async function fetchAndCachePlaylistTracks(cacheKey, fetchUrl, mirrorSource, mir
|
||||||
throw new Error(fullPlaylist.error);
|
throw new Error(fullPlaylist.error);
|
||||||
}
|
}
|
||||||
playlistTrackCache[cacheKey] = fullPlaylist.tracks;
|
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);
|
mirrorPlaylistTracksForSource(mirrorSource, mirrorSourceId, fullPlaylist);
|
||||||
return fullPlaylist;
|
return fullPlaylist;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue