refactor: remove lyrics prefetching UI and optimize admin endpoints

This commit is contained in:
2026-02-07 01:16:03 -05:00
parent 440ef9850f
commit f03aa0be35
2 changed files with 14 additions and 82 deletions

View File

@@ -547,54 +547,6 @@ public class AdminController : ControllerBase
_logger.LogWarning("Playlist {Name} has no JellyfinId configured", config.Name);
}
// Get lyrics completion status
try
{
var tracks = await _playlistFetcher.GetPlaylistTracksAsync(config.Name);
if (tracks.Count > 0)
{
var lyricsWithCount = 0;
var lyricsWithoutCount = 0;
foreach (var track in tracks)
{
var cacheKey = $"lyrics:{track.PrimaryArtist}:{track.Title}:{track.Album}:{track.DurationMs / 1000}";
var existingLyrics = await _cache.GetStringAsync(cacheKey);
if (!string.IsNullOrEmpty(existingLyrics))
{
lyricsWithCount++;
}
else
{
lyricsWithoutCount++;
}
}
playlistInfo["lyricsTotal"] = tracks.Count;
playlistInfo["lyricsCached"] = lyricsWithCount;
playlistInfo["lyricsMissing"] = lyricsWithoutCount;
playlistInfo["lyricsPercentage"] = tracks.Count > 0
? (int)Math.Round((double)lyricsWithCount / tracks.Count * 100)
: 0;
}
else
{
playlistInfo["lyricsTotal"] = 0;
playlistInfo["lyricsCached"] = 0;
playlistInfo["lyricsMissing"] = 0;
playlistInfo["lyricsPercentage"] = 0;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to get lyrics completion for playlist {Name}", config.Name);
playlistInfo["lyricsTotal"] = 0;
playlistInfo["lyricsCached"] = 0;
playlistInfo["lyricsMissing"] = 0;
playlistInfo["lyricsPercentage"] = 0;
}
playlists.Add(playlistInfo);
}
@@ -3471,7 +3423,7 @@ public class LinkPlaylistRequest
/// <summary>
/// GET /api/admin/downloads/file
/// Downloads a specific file
/// Downloads a specific file from the kept folder
/// </summary>
[HttpGet("downloads/file")]
public IActionResult DownloadFile([FromQuery] string path)
@@ -3483,14 +3435,14 @@ public class LinkPlaylistRequest
return BadRequest(new { error = "Path is required" });
}
var downloadPath = _configuration["Library:DownloadPath"] ?? "./downloads";
var fullPath = Path.Combine(downloadPath, path);
var keptPath = _configuration["Library:KeptPath"] ?? "/app/kept";
var fullPath = Path.Combine(keptPath, path);
// Security: Ensure the path is within the download directory
// Security: Ensure the path is within the kept directory
var normalizedFullPath = Path.GetFullPath(fullPath);
var normalizedDownloadPath = Path.GetFullPath(downloadPath);
var normalizedKeptPath = Path.GetFullPath(keptPath);
if (!normalizedFullPath.StartsWith(normalizedDownloadPath))
if (!normalizedFullPath.StartsWith(normalizedKeptPath))
{
return BadRequest(new { error = "Invalid path" });
}

View File

@@ -1414,20 +1414,12 @@
</div>
</td>
<td>
${p.lyricsTotal > 0 ? `
<div style="display:flex;align-items:center;gap:8px;">
<div style="flex:1;background:var(--bg-tertiary);height:12px;border-radius:6px;overflow:hidden;">
<div style="width:${p.lyricsPercentage}%;height:100%;background:${p.lyricsPercentage === 100 ? '#10b981' : '#3b82f6'};transition:width 0.3s;" title="${p.lyricsCached} lyrics cached"></div>
</div>
<span style="font-size:0.85rem;color:var(--text-secondary);font-weight:500;min-width:40px;">${p.lyricsPercentage}%</span>
</div>
` : '<span style="color:var(--text-secondary);font-size:0.85rem;">-</span>'}
<span style="color:var(--text-secondary);font-size:0.85rem;">-</span>
</td>
<td class="cache-age">${p.cacheAge || '-'}</td>
<td>
<button onclick="clearPlaylistCache('${escapeJs(p.name)}')">Clear Cache & Rebuild</button>
<button onclick="matchPlaylistTracks('${escapeJs(p.name)}')">Match Tracks</button>
<button onclick="prefetchLyrics('${escapeJs(p.name)}')">Prefetch Lyrics</button>
<button onclick="viewTracks('${escapeJs(p.name)}')">View</button>
<button class="danger" onclick="removePlaylist('${escapeJs(p.name)}')">Remove</button>
</td>
@@ -1562,16 +1554,20 @@
}
tbody.innerHTML = missingTracks.map(t => {
const artist = (t.artists && t.artists.length > 0) ? t.artists.join(', ') : '';
const searchQuery = `${t.title} ${artist}`;
return `
<tr>
<td><strong>${escapeHtml(t.playlist)}</strong></td>
<td>${escapeHtml(t.title)}</td>
<td>${escapeHtml(t.artist)}</td>
<td>${escapeHtml(artist)}</td>
<td style="color:var(--text-secondary);">${t.album ? escapeHtml(t.album) : '-'}</td>
<td>
<button onclick="openMapToLocal('${escapeJs(t.playlist)}', '${escapeJs(t.spotifyId)}', '${escapeJs(t.title)}', '${escapeJs(t.artist)}')"
<button onclick="searchProvider('${escapeJs(searchQuery)}', 'squidwtf')"
style="margin-right:4px;font-size:0.75rem;padding:4px 8px;background:#3b82f6;border-color:#3b82f6;color:white;">🔍 Search</button>
<button onclick="openMapToLocal('${escapeJs(t.playlist)}', '${escapeJs(t.spotifyId)}', '${escapeJs(t.title)}', '${escapeJs(artist)}')"
style="margin-right:4px;font-size:0.75rem;padding:4px 8px;background:var(--success);border-color:var(--success);">Map to Local</button>
<button onclick="openMapToExternal('${escapeJs(t.playlist)}', '${escapeJs(t.spotifyId)}', '${escapeJs(t.title)}', '${escapeJs(t.artist)}')"
<button onclick="openMapToExternal('${escapeJs(t.playlist)}', '${escapeJs(t.spotifyId)}', '${escapeJs(t.title)}', '${escapeJs(artist)}')"
style="font-size:0.75rem;padding:4px 8px;background:var(--warning);border-color:var(--warning);">Map to External</button>
</td>
</tr>
@@ -1968,22 +1964,6 @@
}
}
async function prefetchLyrics(name) {
try {
showToast(`Prefetching lyrics for ${name}...`, 'info', 5000);
const res = await fetch(`/api/admin/playlists/${encodeURIComponent(name)}/prefetch-lyrics`, { method: 'POST' });
const data = await res.json();
if (res.ok) {
const summary = `Fetched: ${data.fetched}, Cached: ${data.cached}, Missing: ${data.missing}`;
showToast(`✓ Lyrics prefetch complete for ${name}. ${summary}`, 'success', 8000);
} else {
showToast(data.error || 'Failed to prefetch lyrics', 'error');
}
} catch (error) {
showToast('Failed to prefetch lyrics', 'error');
}
}
async function searchProvider(query, provider) {
// Use SquidWTF HiFi API with round-robin base URLs for all searches