diff --git a/allstarr/Controllers/AdminController.cs b/allstarr/Controllers/AdminController.cs index f54c0ea..a8f2103 100644 --- a/allstarr/Controllers/AdminController.cs +++ b/allstarr/Controllers/AdminController.cs @@ -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 } } + /// + /// Prefetch lyrics for a specific playlist + /// + [HttpPost("playlists/{name}/prefetch-lyrics")] + public async Task PrefetchPlaylistLyrics(string name) + { + var decodedName = Uri.UnescapeDataString(name); + + try + { + var lyricsPrefetchService = _serviceProvider.GetService(); + + 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 } diff --git a/allstarr/Services/Lyrics/LyricsPrefetchService.cs b/allstarr/Services/Lyrics/LyricsPrefetchService.cs index e219ada..295a8ab 100644 --- a/allstarr/Services/Lyrics/LyricsPrefetchService.cs +++ b/allstarr/Services/Lyrics/LyricsPrefetchService.cs @@ -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) { diff --git a/allstarr/wwwroot/index.html b/allstarr/wwwroot/index.html index 2c2cd4f..0baa412 100644 --- a/allstarr/wwwroot/index.html +++ b/allstarr/wwwroot/index.html @@ -1274,6 +1274,7 @@ ${p.cacheAge || '-'} + @@ -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 = {