From 2420cd9a236c0680464d57b797c2229946034baf Mon Sep 17 00:00:00 2001 From: Josh Patra Date: Sat, 31 Jan 2026 18:00:24 -0500 Subject: [PATCH] Fix SpotifyMissingTracksFetcher to work with ID-based configuration and add detailed logging --- .../Spotify/SpotifyMissingTracksFetcher.cs | 77 ++++++++++++++----- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs b/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs index 9c6442f..e989d29 100644 --- a/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs +++ b/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs @@ -1,6 +1,7 @@ using allstarr.Models.Settings; using allstarr.Models.Spotify; using allstarr.Services.Common; +using allstarr.Services.Jellyfin; using Microsoft.Extensions.Options; using System.Text.Json; @@ -13,31 +14,35 @@ public class SpotifyMissingTracksFetcher : BackgroundService private readonly IHttpClientFactory _httpClientFactory; private readonly RedisCacheService _cache; private readonly ILogger _logger; + private readonly JellyfinProxyService _proxyService; private bool _hasRunOnce = false; + private Dictionary _playlistIdToName = new(); public SpotifyMissingTracksFetcher( IOptions spotifySettings, IOptions jellyfinSettings, IHttpClientFactory httpClientFactory, RedisCacheService cache, + JellyfinProxyService proxyService, ILogger logger) { _spotifySettings = spotifySettings; _jellyfinSettings = jellyfinSettings; _httpClientFactory = httpClientFactory; _cache = cache; + _proxyService = proxyService; _logger = logger; - - _logger.LogInformation("SpotifyMissingTracksFetcher: Constructor called"); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { + _logger.LogInformation("========================================"); _logger.LogInformation("SpotifyMissingTracksFetcher: Starting up..."); if (!_spotifySettings.Value.Enabled) { - _logger.LogInformation("Spotify playlist injection is disabled"); + _logger.LogInformation("Spotify playlist injection is DISABLED"); + _logger.LogInformation("========================================"); return; } @@ -47,17 +52,21 @@ public class SpotifyMissingTracksFetcher : BackgroundService if (string.IsNullOrEmpty(jellyfinUrl) || string.IsNullOrEmpty(apiKey)) { _logger.LogWarning("Jellyfin URL or API key not configured, Spotify playlist injection disabled"); + _logger.LogInformation("========================================"); return; } - _logger.LogInformation("Spotify missing tracks fetcher started - monitoring {Count} playlists", - _spotifySettings.Value.Playlists.Count); + _logger.LogInformation("Spotify Import ENABLED"); + _logger.LogInformation("Configured Playlist IDs: {Count}", _spotifySettings.Value.PlaylistIds.Count); - foreach (var playlist in _spotifySettings.Value.Playlists) + // Fetch playlist names from Jellyfin + await LoadPlaylistNamesAsync(); + + foreach (var kvp in _playlistIdToName) { - _logger.LogInformation(" - {Name} (SpotifyName: {SpotifyName}, Enabled: {Enabled})", - playlist.Name, playlist.SpotifyName, playlist.Enabled); + _logger.LogInformation(" - {Name} (ID: {Id})", kvp.Value, kvp.Key); } + _logger.LogInformation("========================================"); // Run once on startup if we haven't run in the last 24 hours if (!_hasRunOnce) @@ -98,12 +107,37 @@ public class SpotifyMissingTracksFetcher : BackgroundService } } + private async Task LoadPlaylistNamesAsync() + { + _playlistIdToName.Clear(); + + foreach (var playlistId in _spotifySettings.Value.PlaylistIds) + { + try + { + var playlistInfo = await _proxyService.GetJsonAsync($"Items/{playlistId}", null, null); + if (playlistInfo != null && playlistInfo.RootElement.TryGetProperty("Name", out var nameElement)) + { + var name = nameElement.GetString() ?? ""; + if (!string.IsNullOrEmpty(name)) + { + _playlistIdToName[playlistId] = name; + } + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to get name for playlist {PlaylistId}", playlistId); + } + } + } + private async Task ShouldRunOnStartupAsync() { // Check if any playlist has cached data from the last 24 hours - foreach (var playlist in _spotifySettings.Value.Playlists.Where(p => p.Enabled)) + foreach (var playlistName in _playlistIdToName.Values) { - var cacheKey = $"spotify:missing:{playlist.SpotifyName}"; + var cacheKey = $"spotify:missing:{playlistName}"; if (await _cache.ExistsAsync(cacheKey)) { return false; // Already have recent data @@ -115,9 +149,6 @@ public class SpotifyMissingTracksFetcher : BackgroundService private async Task FetchMissingTracksAsync(CancellationToken cancellationToken) { var settings = _spotifySettings.Value; - var jellyfinUrl = _jellyfinSettings.Value.Url; - var apiKey = _jellyfinSettings.Value.ApiKey; - var now = DateTime.UtcNow; var syncStart = now.Date .AddHours(settings.SyncStartHour) @@ -129,20 +160,23 @@ public class SpotifyMissingTracksFetcher : BackgroundService return; } - foreach (var playlist in settings.Playlists.Where(p => p.Enabled)) + _logger.LogInformation("Within sync window, fetching missing tracks..."); + + foreach (var kvp in _playlistIdToName) { - await FetchPlaylistMissingTracksAsync(playlist, cancellationToken); + await FetchPlaylistMissingTracksAsync(kvp.Value, cancellationToken); } } private async Task FetchPlaylistMissingTracksAsync( - SpotifyPlaylistConfig playlist, + string playlistName, CancellationToken cancellationToken) { - var cacheKey = $"spotify:missing:{playlist.SpotifyName}"; + var cacheKey = $"spotify:missing:{playlistName}"; if (await _cache.ExistsAsync(cacheKey)) { + _logger.LogDebug("Cache already exists for {Playlist}", playlistName); return; } @@ -156,16 +190,19 @@ public class SpotifyMissingTracksFetcher : BackgroundService .AddMinutes(settings.SyncStartMinute); var syncEnd = syncStart.AddHours(settings.SyncWindowHours); + _logger.LogInformation("Searching for missing tracks file for {Playlist}", playlistName); + for (var time = syncStart; time <= syncEnd; time = time.AddMinutes(5)) { if (cancellationToken.IsCancellationRequested) break; - var filename = $"{playlist.SpotifyName}_missing_{time:yyyy-MM-dd_HH-mm}.json"; + var filename = $"{playlistName}_missing_{time:yyyy-MM-dd_HH-mm}.json"; var url = $"{jellyfinUrl}/Viperinius.Plugin.SpotifyImport/MissingTracksFile" + $"?name={Uri.EscapeDataString(filename)}&api_key={apiKey}"; try { + _logger.LogDebug("Trying {Filename}", filename); var response = await httpClient.GetAsync(url, cancellationToken); if (response.IsSuccessStatusCode) { @@ -176,8 +213,8 @@ public class SpotifyMissingTracksFetcher : BackgroundService { await _cache.SetAsync(cacheKey, tracks, TimeSpan.FromHours(24)); _logger.LogInformation( - "Cached {Count} missing tracks for {Playlist} from {Filename}", - tracks.Count, playlist.Name, filename); + "✓ Cached {Count} missing tracks for {Playlist} from {Filename}", + tracks.Count, playlistName, filename); break; } }