Fix external mapping: add Map to External button for external tracks, fetch metadata from provider, set searchQuery for missing tracks

This commit is contained in:
2026-02-05 11:23:01 -05:00
parent 8966fb1fa2
commit 3319c9b21b
2 changed files with 72 additions and 33 deletions

View File

@@ -640,7 +640,7 @@ public class AdminController : ControllerBase
albumArtUrl = track.AlbumArtUrl, albumArtUrl = track.AlbumArtUrl,
isLocal = isLocal, isLocal = isLocal,
externalProvider = externalProvider, externalProvider = externalProvider,
searchQuery = isLocal == false ? $"{track.Title} {track.PrimaryArtist}" : null, searchQuery = isLocal != true ? $"{track.Title} {track.PrimaryArtist}" : null, // Set for both external and missing
isManualMapping = isManualMapping, isManualMapping = isManualMapping,
manualMappingType = manualMappingType, manualMappingType = manualMappingType,
manualMappingId = manualMappingId manualMappingId = manualMappingId
@@ -740,7 +740,7 @@ public class AdminController : ControllerBase
albumArtUrl = track.AlbumArtUrl, albumArtUrl = track.AlbumArtUrl,
isLocal = isLocal, isLocal = isLocal,
externalProvider = externalProvider, externalProvider = externalProvider,
searchQuery = isLocal == false ? $"{track.Title} {track.PrimaryArtist}" : null searchQuery = isLocal != true ? $"{track.Title} {track.PrimaryArtist}" : null // Set for both external and missing
}); });
} }
@@ -1031,11 +1031,15 @@ 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 track details to return to the UI
string? trackTitle = null; string? trackTitle = null;
string? trackArtist = null; string? trackArtist = null;
string? trackAlbum = null; string? trackAlbum = null;
bool isLocalMapping = hasJellyfinMapping;
if (hasJellyfinMapping)
{
// Fetch Jellyfin track details
try try
{ {
var userId = _jellyfinSettings.UserId; var userId = _jellyfinSettings.UserId;
@@ -1071,6 +1075,33 @@ 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");
} }
}
else
{
// Fetch external provider track details
try
{
var metadataService = HttpContext.RequestServices.GetRequiredService<IMusicMetadataService>();
var externalSong = await metadataService.GetSongAsync(request.ExternalProvider!, request.ExternalId!);
if (externalSong != null)
{
trackTitle = externalSong.Title;
trackArtist = externalSong.Artist;
trackAlbum = externalSong.Album;
_logger.LogInformation("✓ Fetched external track metadata: {Title} by {Artist}", trackTitle, trackArtist);
}
else
{
_logger.LogWarning("Failed to fetch external track metadata for {Provider} ID {Id}",
request.ExternalProvider, request.ExternalId);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to fetch external track metadata, but mapping was saved");
}
}
// Trigger immediate playlist rebuild with the new mapping // Trigger immediate playlist rebuild with the new mapping
if (_matchingService != null) if (_matchingService != null)
@@ -1104,11 +1135,12 @@ public class AdminController : ControllerBase
// Return success with track details if available // Return success with track details if available
var mappedTrack = new var mappedTrack = new
{ {
id = request.JellyfinId, id = hasJellyfinMapping ? request.JellyfinId : request.ExternalId,
title = trackTitle ?? "Unknown", title = trackTitle ?? "Unknown",
artist = trackArtist ?? "Unknown", artist = trackArtist ?? "Unknown",
album = trackAlbum ?? "Unknown", album = trackAlbum ?? "Unknown",
isLocal = true isLocal = isLocalMapping,
externalProvider = hasExternalMapping ? request.ExternalProvider : null
}; };
return Ok(new return Ok(new

View File

@@ -1799,7 +1799,7 @@
if (t.isManualMapping && t.manualMappingType === 'external') { if (t.isManualMapping && t.manualMappingType === 'external') {
statusBadge += '<span class="status-badge" style="font-size:0.75rem;padding:2px 8px;margin-left:4px;background:var(--info);color:white;"><span class="status-dot" style="background:white;"></span>Manual</span>'; statusBadge += '<span class="status-badge" style="font-size:0.75rem;padding:2px 8px;margin-left:4px;background:var(--info);color:white;"><span class="status-dot" style="background:white;"></span>Manual</span>';
} }
// Add manual map button for external tracks using data attributes // Add both mapping buttons for external tracks using data attributes
const firstArtist = (t.artists && t.artists.length > 0) ? t.artists[0] : ''; const firstArtist = (t.artists && t.artists.length > 0) ? t.artists[0] : '';
mapButton = `<button class="small map-track-btn" mapButton = `<button class="small map-track-btn"
data-playlist-name="${escapeHtml(name)}" data-playlist-name="${escapeHtml(name)}"
@@ -1807,7 +1807,14 @@
data-title="${escapeHtml(t.title || '')}" data-title="${escapeHtml(t.title || '')}"
data-artist="${escapeHtml(firstArtist)}" data-artist="${escapeHtml(firstArtist)}"
data-spotify-id="${escapeHtml(t.spotifyId || '')}" data-spotify-id="${escapeHtml(t.spotifyId || '')}"
style="margin-left:8px;font-size:0.75rem;padding:4px 8px;">Map to Local</button>`; style="margin-left:8px;font-size:0.75rem;padding:4px 8px;">Map to Local</button>
<button class="small map-external-btn"
data-playlist-name="${escapeHtml(name)}"
data-position="${t.position}"
data-title="${escapeHtml(t.title || '')}"
data-artist="${escapeHtml(firstArtist)}"
data-spotify-id="${escapeHtml(t.spotifyId || '')}"
style="margin-left:4px;font-size:0.75rem;padding:4px 8px;background:var(--warning);border-color:var(--warning);">Map to External</button>`;
} else { } else {
// isLocal is null/undefined - track is missing (not found locally or externally) // isLocal is null/undefined - track is missing (not found locally or externally)
statusBadge = '<span class="status-badge" style="font-size:0.75rem;padding:2px 8px;margin-left:8px;background:var(--bg-tertiary);color:var(--text-secondary);"><span class="status-dot" style="background:var(--text-secondary);"></span>Missing</span>'; statusBadge = '<span class="status-badge" style="font-size:0.75rem;padding:2px 8px;margin-left:8px;background:var(--bg-tertiary);color:var(--text-secondary);"><span class="status-dot" style="background:var(--text-secondary);"></span>Missing</span>';