diff --git a/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs b/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs index c31c19a..b0e097c 100644 --- a/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs +++ b/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs @@ -296,18 +296,35 @@ public class SpotifyMissingTracksFetcher : BackgroundService } _logger.LogInformation("Processing {Count} playlists", _playlistIdToName.Count); + + // Track when we find files to optimize search for other playlists + DateTime? firstFoundTime = null; + var foundPlaylists = new HashSet(); + foreach (var kvp in _playlistIdToName) { _logger.LogInformation("Fetching playlist: {Name}", kvp.Value); - await FetchPlaylistMissingTracksAsync(kvp.Value, cancellationToken); + var foundTime = await FetchPlaylistMissingTracksAsync(kvp.Value, cancellationToken, firstFoundTime); + + if (foundTime.HasValue) + { + foundPlaylists.Add(kvp.Value); + if (!firstFoundTime.HasValue) + { + firstFoundTime = foundTime; + _logger.LogInformation(" → Will search within ±1h of {Time:yyyy-MM-dd HH:mm} for remaining playlists", firstFoundTime.Value); + } + } } - _logger.LogInformation("=== FINISHED FETCHING MISSING TRACKS ==="); + _logger.LogInformation("=== FINISHED FETCHING MISSING TRACKS ({Found}/{Total} playlists found) ===", + foundPlaylists.Count, _playlistIdToName.Count); } - private async Task FetchPlaylistMissingTracksAsync( + private async Task FetchPlaylistMissingTracksAsync( string playlistName, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + DateTime? hintTime = null) { var cacheKey = $"spotify:missing:{playlistName}"; @@ -337,7 +354,7 @@ public class SpotifyMissingTracksFetcher : BackgroundService if (string.IsNullOrEmpty(jellyfinUrl) || string.IsNullOrEmpty(apiKey)) { _logger.LogWarning(" Jellyfin URL or API key not configured, skipping fetch"); - return; + return null; } var httpClient = _httpClientFactory.CreateClient(); @@ -351,6 +368,46 @@ public class SpotifyMissingTracksFetcher : BackgroundService var found = false; DateTime? foundFileTime = null; + // If we have a hint time from another playlist, search ±1 hour around it first + if (hintTime.HasValue) + { + _logger.LogInformation(" Hint: Searching ±1h around {Time:yyyy-MM-dd HH:mm} (from another playlist)", hintTime.Value); + + // Search ±60 minutes around the hint time + for (var minuteOffset = 0; minuteOffset <= 60; minuteOffset++) + { + if (cancellationToken.IsCancellationRequested) break; + + // Try both forward and backward from hint + if (minuteOffset > 0) + { + // Try forward + var timeForward = hintTime.Value.AddMinutes(minuteOffset); + var resultForward = await TryFetchMissingTracksFile(playlistName, timeForward, jellyfinUrl, apiKey, httpClient, cancellationToken); + if (resultForward.found) + { + found = true; + foundFileTime = resultForward.fileTime; + _logger.LogInformation(" ✓ Found using hint (+{Minutes}min from hint)", minuteOffset); + return foundFileTime; + } + } + + // Try backward + var timeBackward = hintTime.Value.AddMinutes(-minuteOffset); + var resultBackward = await TryFetchMissingTracksFile(playlistName, timeBackward, jellyfinUrl, apiKey, httpClient, cancellationToken); + if (resultBackward.found) + { + found = true; + foundFileTime = resultBackward.fileTime; + _logger.LogInformation(" ✓ Found using hint (-{Minutes}min from hint)", minuteOffset); + return foundFileTime; + } + } + + _logger.LogInformation(" Not found within ±1h of hint, doing full search..."); + } + // First search forward 24 hours (most likely to find newest files with timezone ahead) _logger.LogInformation(" Phase 1: Searching forward 24 hours..."); @@ -365,7 +422,7 @@ public class SpotifyMissingTracksFetcher : BackgroundService { found = true; foundFileTime = result.fileTime; - break; // Found newest file, stop searching + return foundFileTime; } // Small delay every 60 requests to avoid rate limiting @@ -391,7 +448,7 @@ public class SpotifyMissingTracksFetcher : BackgroundService { found = true; foundFileTime = result.fileTime; - break; + return foundFileTime; } // Small delay every 60 requests to avoid rate limiting @@ -438,10 +495,8 @@ public class SpotifyMissingTracksFetcher : BackgroundService _logger.LogWarning(" No existing cache to keep - playlist will be empty until tracks are found"); } } - else if (foundFileTime.HasValue) - { - _logger.LogInformation(" ✓ Updated cache with newer file from {Time}", foundFileTime.Value); - } + + return foundFileTime; } private async Task<(bool found, DateTime? fileTime)> TryFetchMissingTracksFile(