mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
refactor: remove lyrics prefetching UI and optimize admin endpoints
This commit is contained in:
@@ -547,54 +547,6 @@ public class AdminController : ControllerBase
|
|||||||
_logger.LogWarning("Playlist {Name} has no JellyfinId configured", config.Name);
|
_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);
|
playlists.Add(playlistInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3471,7 +3423,7 @@ public class LinkPlaylistRequest
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GET /api/admin/downloads/file
|
/// GET /api/admin/downloads/file
|
||||||
/// Downloads a specific file
|
/// Downloads a specific file from the kept folder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HttpGet("downloads/file")]
|
[HttpGet("downloads/file")]
|
||||||
public IActionResult DownloadFile([FromQuery] string path)
|
public IActionResult DownloadFile([FromQuery] string path)
|
||||||
@@ -3483,14 +3435,14 @@ public class LinkPlaylistRequest
|
|||||||
return BadRequest(new { error = "Path is required" });
|
return BadRequest(new { error = "Path is required" });
|
||||||
}
|
}
|
||||||
|
|
||||||
var downloadPath = _configuration["Library:DownloadPath"] ?? "./downloads";
|
var keptPath = _configuration["Library:KeptPath"] ?? "/app/kept";
|
||||||
var fullPath = Path.Combine(downloadPath, path);
|
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 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" });
|
return BadRequest(new { error = "Invalid path" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1414,20 +1414,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
${p.lyricsTotal > 0 ? `
|
<span style="color:var(--text-secondary);font-size:0.85rem;">-</span>
|
||||||
<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>'}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="cache-age">${p.cacheAge || '-'}</td>
|
<td class="cache-age">${p.cacheAge || '-'}</td>
|
||||||
<td>
|
<td>
|
||||||
<button onclick="clearPlaylistCache('${escapeJs(p.name)}')">Clear Cache & Rebuild</button>
|
<button onclick="clearPlaylistCache('${escapeJs(p.name)}')">Clear Cache & Rebuild</button>
|
||||||
<button onclick="matchPlaylistTracks('${escapeJs(p.name)}')">Match Tracks</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 onclick="viewTracks('${escapeJs(p.name)}')">View</button>
|
||||||
<button class="danger" onclick="removePlaylist('${escapeJs(p.name)}')">Remove</button>
|
<button class="danger" onclick="removePlaylist('${escapeJs(p.name)}')">Remove</button>
|
||||||
</td>
|
</td>
|
||||||
@@ -1562,16 +1554,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
tbody.innerHTML = missingTracks.map(t => {
|
tbody.innerHTML = missingTracks.map(t => {
|
||||||
|
const artist = (t.artists && t.artists.length > 0) ? t.artists.join(', ') : '';
|
||||||
|
const searchQuery = `${t.title} ${artist}`;
|
||||||
return `
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>${escapeHtml(t.playlist)}</strong></td>
|
<td><strong>${escapeHtml(t.playlist)}</strong></td>
|
||||||
<td>${escapeHtml(t.title)}</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 style="color:var(--text-secondary);">${t.album ? escapeHtml(t.album) : '-'}</td>
|
||||||
<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>
|
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>
|
style="font-size:0.75rem;padding:4px 8px;background:var(--warning);border-color:var(--warning);">Map to External</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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) {
|
async function searchProvider(query, provider) {
|
||||||
// Use SquidWTF HiFi API with round-robin base URLs for all searches
|
// Use SquidWTF HiFi API with round-robin base URLs for all searches
|
||||||
|
|||||||
Reference in New Issue
Block a user