Fix GetPlaylists to use pre-built cache with manual mappings for accurate counts

This commit is contained in:
2026-02-04 18:49:12 -05:00
parent 1d31784ff8
commit 5680b9c7c9

View File

@@ -247,99 +247,137 @@ public class AdminController : ControllerBase
if (jellyfinDoc.RootElement.TryGetProperty("Items", out var items))
{
// Build list of local tracks from Jellyfin (match by name only)
var localTracks = new List<(string Title, string Artist)>();
foreach (var item in items.EnumerateArray())
{
var title = item.TryGetProperty("Name", out var nameEl) ? nameEl.GetString() ?? "" : "";
var artist = "";
if (item.TryGetProperty("Artists", out var artistsEl) && artistsEl.GetArrayLength() > 0)
{
artist = artistsEl[0].GetString() ?? "";
}
else if (item.TryGetProperty("AlbumArtist", out var albumArtistEl))
{
artist = albumArtistEl.GetString() ?? "";
}
if (!string.IsNullOrEmpty(title))
{
localTracks.Add((title, artist));
}
}
// Get Spotify tracks to match against
var spotifyTracks = await _playlistFetcher.GetPlaylistTracksAsync(config.Name);
// Get matched external tracks cache once
var matchedTracksKey = $"spotify:matched:ordered:{config.Name}";
var matchedTracks = await _cache.GetAsync<List<MatchedTrack>>(matchedTracksKey);
var matchedSpotifyIds = new HashSet<string>(
matchedTracks?.Select(m => m.SpotifyId) ?? Enumerable.Empty<string>()
);
// Try to use the pre-built playlist cache first (includes manual mappings!)
var playlistItemsCacheKey = $"spotify:playlist:items:{config.Name}";
var cachedPlaylistItems = await _cache.GetAsync<List<Dictionary<string, object?>>>(playlistItemsCacheKey);
var localCount = 0;
var externalMatchedCount = 0;
var externalMissingCount = 0;
// Match each Spotify track to determine if it's local, external, or missing
foreach (var track in spotifyTracks)
if (cachedPlaylistItems != null && cachedPlaylistItems.Count > 0)
{
var isLocal = false;
// Use the pre-built cache which respects manual mappings
var localCount = 0;
var externalCount = 0;
if (localTracks.Count > 0)
foreach (var item in cachedPlaylistItems)
{
var bestMatch = localTracks
.Select(local => new
{
Local = local,
TitleScore = FuzzyMatcher.CalculateSimilarity(track.Title, local.Title),
ArtistScore = FuzzyMatcher.CalculateSimilarity(track.PrimaryArtist, local.Artist)
})
.Select(x => new
{
x.Local,
x.TitleScore,
x.ArtistScore,
TotalScore = (x.TitleScore * 0.7) + (x.ArtistScore * 0.3)
})
.OrderByDescending(x => x.TotalScore)
.FirstOrDefault();
// Use 70% threshold (same as playback matching)
if (bestMatch != null && bestMatch.TotalScore >= 70)
// Check if it's a local track (has Path) or external (no Path)
if (item.TryGetValue("Path", out var pathObj) && pathObj != null)
{
isLocal = true;
}
}
if (isLocal)
{
localCount++;
}
else
{
// Check if external track is matched
if (matchedSpotifyIds.Contains(track.SpotifyId))
{
externalMatchedCount++;
localCount++;
}
else
{
externalMissingCount++;
externalCount++;
}
}
var externalMissingCount = spotifyTracks.Count - cachedPlaylistItems.Count;
if (externalMissingCount < 0) externalMissingCount = 0;
playlistInfo["localTracks"] = localCount;
playlistInfo["externalMatched"] = externalCount;
playlistInfo["externalMissing"] = externalMissingCount;
playlistInfo["externalTotal"] = externalCount + externalMissingCount;
playlistInfo["totalInJellyfin"] = cachedPlaylistItems.Count;
_logger.LogDebug("Playlist {Name} (from cache): {Total} Spotify tracks, {Local} local, {ExtMatched} external matched, {ExtMissing} external missing",
config.Name, spotifyTracks.Count, localCount, externalCount, externalMissingCount);
}
else
{
// Fallback: Build list of local tracks from Jellyfin (match by name only)
var localTracks = new List<(string Title, string Artist)>();
foreach (var item in items.EnumerateArray())
{
var title = item.TryGetProperty("Name", out var nameEl) ? nameEl.GetString() ?? "" : "";
var artist = "";
if (item.TryGetProperty("Artists", out var artistsEl) && artistsEl.GetArrayLength() > 0)
{
artist = artistsEl[0].GetString() ?? "";
}
else if (item.TryGetProperty("AlbumArtist", out var albumArtistEl))
{
artist = albumArtistEl.GetString() ?? "";
}
if (!string.IsNullOrEmpty(title))
{
localTracks.Add((title, artist));
}
}
// Get matched external tracks cache once
var matchedTracksKey = $"spotify:matched:ordered:{config.Name}";
var matchedTracks = await _cache.GetAsync<List<MatchedTrack>>(matchedTracksKey);
var matchedSpotifyIds = new HashSet<string>(
matchedTracks?.Select(m => m.SpotifyId) ?? Enumerable.Empty<string>()
);
var localCount = 0;
var externalMatchedCount = 0;
var externalMissingCount = 0;
// Match each Spotify track to determine if it's local, external, or missing
foreach (var track in spotifyTracks)
{
var isLocal = false;
if (localTracks.Count > 0)
{
var bestMatch = localTracks
.Select(local => new
{
Local = local,
TitleScore = FuzzyMatcher.CalculateSimilarity(track.Title, local.Title),
ArtistScore = FuzzyMatcher.CalculateSimilarity(track.PrimaryArtist, local.Artist)
})
.Select(x => new
{
x.Local,
x.TitleScore,
x.ArtistScore,
TotalScore = (x.TitleScore * 0.7) + (x.ArtistScore * 0.3)
})
.OrderByDescending(x => x.TotalScore)
.FirstOrDefault();
// Use 70% threshold (same as playback matching)
if (bestMatch != null && bestMatch.TotalScore >= 70)
{
isLocal = true;
}
}
if (isLocal)
{
localCount++;
}
else
{
// Check if external track is matched
if (matchedSpotifyIds.Contains(track.SpotifyId))
{
externalMatchedCount++;
}
else
{
externalMissingCount++;
}
}
}
playlistInfo["localTracks"] = localCount;
playlistInfo["externalMatched"] = externalMatchedCount;
playlistInfo["externalMissing"] = externalMissingCount;
playlistInfo["externalTotal"] = externalMatchedCount + externalMissingCount;
playlistInfo["totalInJellyfin"] = localCount + externalMatchedCount;
_logger.LogDebug("Playlist {Name} (fallback): {Total} Spotify tracks, {Local} local, {ExtMatched} external matched, {ExtMissing} external missing",
config.Name, spotifyTracks.Count, localCount, externalMatchedCount, externalMissingCount);
}
playlistInfo["localTracks"] = localCount;
playlistInfo["externalMatched"] = externalMatchedCount;
playlistInfo["externalMissing"] = externalMissingCount;
playlistInfo["externalTotal"] = externalMatchedCount + externalMissingCount;
playlistInfo["totalInJellyfin"] = localCount + externalMatchedCount;
_logger.LogDebug("Playlist {Name}: {Total} Spotify tracks, {Local} local, {ExtMatched} external matched, {ExtMissing} external missing",
config.Name, spotifyTrackCount, localCount, externalMatchedCount, externalMissingCount);
}
else
{