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,
isLocal = isLocal,
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,
manualMappingType = manualMappingType,
manualMappingId = manualMappingId
@@ -740,7 +740,7 @@ public class AdminController : ControllerBase
albumArtUrl = track.AlbumArtUrl,
isLocal = isLocal,
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,45 +1031,76 @@ public class AdminController : ControllerBase
_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? trackArtist = null;
string? trackAlbum = null;
bool isLocalMapping = hasJellyfinMapping;
try
if (hasJellyfinMapping)
{
var userId = _jellyfinSettings.UserId;
var trackUrl = $"{_jellyfinSettings.Url}/Items/{request.JellyfinId}";
if (!string.IsNullOrEmpty(userId))
// Fetch Jellyfin track details
try
{
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)
{
var trackData = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(trackData);
var track = doc.RootElement;
var userId = _jellyfinSettings.UserId;
var trackUrl = $"{_jellyfinSettings.Url}/Items/{request.JellyfinId}";
if (!string.IsNullOrEmpty(userId))
{
trackUrl += $"?UserId={userId}";
}
trackTitle = track.TryGetProperty("Name", out var nameEl) ? nameEl.GetString() : null;
trackArtist = track.TryGetProperty("AlbumArtist", out var artistEl) ? artistEl.GetString() :
(track.TryGetProperty("Artists", out var artistsEl) && artistsEl.GetArrayLength() > 0
? artistsEl[0].GetString() : null);
trackAlbum = track.TryGetProperty("Album", out var albumEl) ? albumEl.GetString() : null;
var trackRequest = new HttpRequestMessage(HttpMethod.Get, trackUrl);
trackRequest.Headers.Add("X-Emby-Authorization", GetJellyfinAuthHeader());
var response = await _jellyfinHttpClient.SendAsync(trackRequest);
if (response.IsSuccessStatusCode)
{
var trackData = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(trackData);
var track = doc.RootElement;
trackTitle = track.TryGetProperty("Name", out var nameEl) ? nameEl.GetString() : null;
trackArtist = track.TryGetProperty("AlbumArtist", out var artistEl) ? artistEl.GetString() :
(track.TryGetProperty("Artists", out var artistsEl) && artistsEl.GetArrayLength() > 0
? artistsEl[0].GetString() : null);
trackAlbum = track.TryGetProperty("Album", out var albumEl) ? albumEl.GetString() : null;
}
else
{
_logger.LogWarning("Failed to fetch Jellyfin track {Id}: {StatusCode}", request.JellyfinId, response.StatusCode);
}
}
else
catch (Exception ex)
{
_logger.LogWarning("Failed to fetch Jellyfin track {Id}: {StatusCode}", request.JellyfinId, response.StatusCode);
_logger.LogWarning(ex, "Failed to fetch mapped track details, but mapping was saved");
}
}
catch (Exception ex)
else
{
_logger.LogWarning(ex, "Failed to fetch mapped track details, but mapping was saved");
// 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
@@ -1104,11 +1135,12 @@ public class AdminController : ControllerBase
// Return success with track details if available
var mappedTrack = new
{
id = request.JellyfinId,
id = hasJellyfinMapping ? request.JellyfinId : request.ExternalId,
title = trackTitle ?? "Unknown",
artist = trackArtist ?? "Unknown",
album = trackAlbum ?? "Unknown",
isLocal = true
isLocal = isLocalMapping,
externalProvider = hasExternalMapping ? request.ExternalProvider : null
};
return Ok(new

View File

@@ -1799,7 +1799,7 @@
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>';
}
// 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] : '';
mapButton = `<button class="small map-track-btn"
data-playlist-name="${escapeHtml(name)}"
@@ -1807,7 +1807,14 @@
data-title="${escapeHtml(t.title || '')}"
data-artist="${escapeHtml(firstArtist)}"
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 {
// 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>';