Add lyrics prefetch endpoint and UI button: prefetch lyrics for individual playlists with progress feedback

This commit is contained in:
2026-02-05 11:45:36 -05:00
parent 8555b67a38
commit 3b8d83b43e
3 changed files with 62 additions and 1 deletions

View File

@@ -38,6 +38,7 @@ public class AdminController : ControllerBase
private readonly RedisCacheService _cache;
private readonly HttpClient _jellyfinHttpClient;
private readonly IWebHostEnvironment _environment;
private readonly IServiceProvider _serviceProvider;
private readonly string _envFilePath;
private const string CacheDirectory = "/app/cache/spotify";
@@ -56,6 +57,7 @@ public class AdminController : ControllerBase
SpotifyPlaylistFetcher playlistFetcher,
RedisCacheService cache,
IHttpClientFactory httpClientFactory,
IServiceProvider serviceProvider,
SpotifyTrackMatchingService? matchingService = null)
{
_logger = logger;
@@ -73,6 +75,7 @@ public class AdminController : ControllerBase
_matchingService = matchingService;
_cache = cache;
_jellyfinHttpClient = httpClientFactory.CreateClient();
_serviceProvider = serviceProvider;
// .env file path is always /app/.env in Docker (mounted from host)
// In development, it's in the parent directory of ContentRootPath
@@ -2621,6 +2624,46 @@ public class AdminController : ControllerBase
}
}
/// <summary>
/// Prefetch lyrics for a specific playlist
/// </summary>
[HttpPost("playlists/{name}/prefetch-lyrics")]
public async Task<IActionResult> PrefetchPlaylistLyrics(string name)
{
var decodedName = Uri.UnescapeDataString(name);
try
{
var lyricsPrefetchService = _serviceProvider.GetService<allstarr.Services.Lyrics.LyricsPrefetchService>();
if (lyricsPrefetchService == null)
{
return StatusCode(500, new { error = "Lyrics prefetch service not available" });
}
_logger.LogInformation("Starting lyrics prefetch for playlist: {Playlist}", decodedName);
var (fetched, cached, missing) = await lyricsPrefetchService.PrefetchPlaylistLyricsAsync(
decodedName,
HttpContext.RequestAborted);
return Ok(new
{
message = "Lyrics prefetch complete",
playlist = decodedName,
fetched,
cached,
missing,
total = fetched + cached + missing
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to prefetch lyrics for playlist {Playlist}", decodedName);
return StatusCode(500, new { error = $"Failed to prefetch lyrics: {ex.Message}" });
}
}
#endregion
}

View File

@@ -107,7 +107,7 @@ public class LyricsPrefetchService : BackgroundService
totalFetched, totalCached, totalMissing);
}
private async Task<(int Fetched, int Cached, int Missing)> PrefetchPlaylistLyricsAsync(
public async Task<(int Fetched, int Cached, int Missing)> PrefetchPlaylistLyricsAsync(
string playlistName,
CancellationToken cancellationToken)
{

View File

@@ -1274,6 +1274,7 @@
<td class="cache-age">${p.cacheAge || '-'}</td>
<td>
<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>
@@ -1568,6 +1569,23 @@
}
}
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');
}
}
function searchProvider(query, provider) {
// Provider-specific search URLs
const searchUrls = {