mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Fix manual mapping: add immediate playlist rebuild and manual mapping priority in cache builder
This commit is contained in:
@@ -696,10 +696,23 @@ public class AdminController : ControllerBase
|
|||||||
_logger.LogInformation("Cleared playlist caches for {Playlist} to force rebuild", decodedName);
|
_logger.LogInformation("Cleared playlist caches for {Playlist} to force rebuild", decodedName);
|
||||||
|
|
||||||
// Fetch the mapped Jellyfin track details to return to the UI
|
// Fetch the mapped Jellyfin track details to return to the UI
|
||||||
|
string? trackTitle = null;
|
||||||
|
string? trackArtist = null;
|
||||||
|
string? trackAlbum = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var trackUrl = $"{_jellyfinSettings.Url}/Items/{request.JellyfinId}?api_key={_jellyfinSettings.ApiKey}";
|
var userId = _jellyfinSettings.UserId;
|
||||||
var response = await _jellyfinHttpClient.GetAsync(trackUrl);
|
var trackUrl = $"{_jellyfinSettings.Url}/Items/{request.JellyfinId}";
|
||||||
|
if (!string.IsNullOrEmpty(userId))
|
||||||
|
{
|
||||||
|
trackUrl += $"?UserId={userId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
var trackRequest = new HttpRequestMessage(HttpMethod.Get, trackUrl);
|
||||||
|
trackRequest.Headers.Add("X-Emby-Authorization", GetJellyfinAuthHeader());
|
||||||
|
|
||||||
|
var response = await _jellyfinHttpClient.SendAsync(trackRequest);
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
@@ -707,18 +720,15 @@ public class AdminController : ControllerBase
|
|||||||
using var doc = JsonDocument.Parse(trackData);
|
using var doc = JsonDocument.Parse(trackData);
|
||||||
var track = doc.RootElement;
|
var track = doc.RootElement;
|
||||||
|
|
||||||
var mappedTrack = new
|
trackTitle = track.TryGetProperty("Name", out var nameEl) ? nameEl.GetString() : null;
|
||||||
{
|
trackArtist = track.TryGetProperty("AlbumArtist", out var artistEl) ? artistEl.GetString() :
|
||||||
id = request.JellyfinId,
|
|
||||||
title = track.TryGetProperty("Name", out var nameEl) ? nameEl.GetString() : "",
|
|
||||||
artist = track.TryGetProperty("AlbumArtist", out var artistEl) ? artistEl.GetString() :
|
|
||||||
(track.TryGetProperty("Artists", out var artistsEl) && artistsEl.GetArrayLength() > 0
|
(track.TryGetProperty("Artists", out var artistsEl) && artistsEl.GetArrayLength() > 0
|
||||||
? artistsEl[0].GetString() : ""),
|
? artistsEl[0].GetString() : null);
|
||||||
album = track.TryGetProperty("Album", out var albumEl) ? albumEl.GetString() : "",
|
trackAlbum = track.TryGetProperty("Album", out var albumEl) ? albumEl.GetString() : null;
|
||||||
isLocal = true
|
}
|
||||||
};
|
else
|
||||||
|
{
|
||||||
return Ok(new { message = "Mapping saved successfully", track = mappedTrack });
|
_logger.LogWarning("Failed to fetch Jellyfin track {Id}: {StatusCode}", request.JellyfinId, response.StatusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -726,7 +736,46 @@ public class AdminController : ControllerBase
|
|||||||
_logger.LogWarning(ex, "Failed to fetch mapped track details, but mapping was saved");
|
_logger.LogWarning(ex, "Failed to fetch mapped track details, but mapping was saved");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(new { message = "Mapping saved successfully" });
|
// Trigger immediate playlist rebuild with the new mapping
|
||||||
|
if (_matchingService != null)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Triggering immediate playlist rebuild for {Playlist} with new manual mapping", decodedName);
|
||||||
|
|
||||||
|
// Run in background so we don't block the response
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _matchingService.TriggerMatchingForPlaylistAsync(decodedName);
|
||||||
|
_logger.LogInformation("✓ Playlist {Playlist} rebuilt successfully with manual mapping", decodedName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to rebuild playlist {Playlist} after manual mapping", decodedName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Matching service not available - playlist will rebuild on next scheduled run");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return success with track details if available
|
||||||
|
var mappedTrack = new
|
||||||
|
{
|
||||||
|
id = request.JellyfinId,
|
||||||
|
title = trackTitle ?? "Unknown",
|
||||||
|
artist = trackArtist ?? "Unknown",
|
||||||
|
album = trackAlbum ?? "Unknown",
|
||||||
|
isLocal = true
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
message = "Mapping saved and playlist rebuild triggered",
|
||||||
|
track = mappedTrack,
|
||||||
|
rebuildTriggered = _matchingService != null
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -754,9 +754,42 @@ public class SpotifyTrackMatchingService : BackgroundService
|
|||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested) break;
|
if (cancellationToken.IsCancellationRequested) break;
|
||||||
|
|
||||||
// Try to find matching Jellyfin item by fuzzy matching
|
// FIRST: Check for manual mapping
|
||||||
|
var manualMappingKey = $"spotify:manual-map:{playlistName}:{spotifyTrack.SpotifyId}";
|
||||||
|
var manualJellyfinId = await _cache.GetAsync<string>(manualMappingKey);
|
||||||
|
|
||||||
JsonElement? matchedJellyfinItem = null;
|
JsonElement? matchedJellyfinItem = null;
|
||||||
string? matchedKey = null;
|
string? matchedKey = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(manualJellyfinId))
|
||||||
|
{
|
||||||
|
// Manual mapping exists - fetch the Jellyfin item by ID
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var itemUrl = $"Items/{manualJellyfinId}?UserId={userId}";
|
||||||
|
var (itemResponse, itemStatusCode) = await proxyService.GetJsonAsync(itemUrl, null, headers);
|
||||||
|
|
||||||
|
if (itemStatusCode == 200 && itemResponse != null)
|
||||||
|
{
|
||||||
|
matchedJellyfinItem = itemResponse.RootElement;
|
||||||
|
_logger.LogDebug("✓ Using manual mapping for {Title}: Jellyfin ID {Id}",
|
||||||
|
spotifyTrack.Title, manualJellyfinId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Manual mapping points to invalid Jellyfin ID {Id} for {Title}",
|
||||||
|
manualJellyfinId, spotifyTrack.Title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to fetch manually mapped Jellyfin item {Id}", manualJellyfinId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SECOND: If no manual mapping, try fuzzy matching
|
||||||
|
if (!matchedJellyfinItem.HasValue)
|
||||||
|
{
|
||||||
double bestScore = 0;
|
double bestScore = 0;
|
||||||
|
|
||||||
foreach (var kvp in jellyfinItemsByName)
|
foreach (var kvp in jellyfinItemsByName)
|
||||||
@@ -782,15 +815,19 @@ public class SpotifyTrackMatchingService : BackgroundService
|
|||||||
matchedKey = kvp.Key;
|
matchedKey = kvp.Key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (matchedJellyfinItem.HasValue && matchedKey != null)
|
if (matchedJellyfinItem.HasValue)
|
||||||
{
|
{
|
||||||
// Use the raw Jellyfin item (preserves ALL metadata)
|
// Use the raw Jellyfin item (preserves ALL metadata)
|
||||||
var itemDict = JsonSerializer.Deserialize<Dictionary<string, object?>>(matchedJellyfinItem.Value.GetRawText());
|
var itemDict = JsonSerializer.Deserialize<Dictionary<string, object?>>(matchedJellyfinItem.Value.GetRawText());
|
||||||
if (itemDict != null)
|
if (itemDict != null)
|
||||||
{
|
{
|
||||||
finalItems.Add(itemDict);
|
finalItems.Add(itemDict);
|
||||||
|
if (matchedKey != null)
|
||||||
|
{
|
||||||
usedJellyfinItems.Add(matchedKey);
|
usedJellyfinItems.Add(matchedKey);
|
||||||
|
}
|
||||||
localUsedCount++;
|
localUsedCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user