diff --git a/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs b/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs index 0143fee..3d46347 100644 --- a/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs +++ b/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs @@ -146,12 +146,13 @@ public class SpotifyMissingTracksFetcher : BackgroundService .AddMinutes(settings.SyncStartMinute); var syncEnd = syncStart.AddHours(settings.SyncWindowHours); - if (now < syncStart || now > syncEnd) + // Only run after the sync window has passed + if (now < syncEnd) { return; } - _logger.LogInformation("Within sync window, fetching missing tracks..."); + _logger.LogInformation("Sync window passed, searching last 24 hours for missing tracks..."); foreach (var kvp in _playlistIdToName) { @@ -175,46 +176,106 @@ public class SpotifyMissingTracksFetcher : BackgroundService var jellyfinUrl = _jellyfinSettings.Value.Url; var apiKey = _jellyfinSettings.Value.ApiKey; var httpClient = _httpClientFactory.CreateClient(); - var today = DateTime.UtcNow.Date; - var syncStart = today + + // Start from the configured sync time (most likely time) + var now = DateTime.UtcNow; + var todaySync = now.Date .AddHours(settings.SyncStartHour) .AddMinutes(settings.SyncStartMinute); - var syncEnd = syncStart.AddHours(settings.SyncWindowHours); + + // If we haven't reached today's sync time yet, start from yesterday's sync time + var syncTime = now >= todaySync ? todaySync : todaySync.AddDays(-1); - _logger.LogInformation("Searching for missing tracks file for {Playlist}", playlistName); + _logger.LogInformation("Searching ±12 hours around {SyncTime} for {Playlist}", + syncTime, playlistName); - for (var time = syncStart; time <= syncEnd; time = time.AddMinutes(5)) + var found = false; + + // Search forward 12 hours from sync time + for (var minutesAhead = 0; minutesAhead <= 720; minutesAhead++) // 720 minutes = 12 hours { if (cancellationToken.IsCancellationRequested) break; - 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 + var time = syncTime.AddMinutes(minutesAhead); + if (await TryFetchMissingTracksFile(playlistName, time, jellyfinUrl, apiKey, httpClient, cancellationToken)) { - _logger.LogDebug("Trying {Filename}", filename); - var response = await httpClient.GetAsync(url, cancellationToken); - if (response.IsSuccessStatusCode) - { - var json = await response.Content.ReadAsStringAsync(cancellationToken); - var tracks = ParseMissingTracks(json); - - if (tracks.Count > 0) - { - await _cache.SetAsync(cacheKey, tracks, TimeSpan.FromHours(24)); - _logger.LogInformation( - "✓ Cached {Count} missing tracks for {Playlist} from {Filename}", - tracks.Count, playlistName, filename); - break; - } - } + found = true; + break; } - catch (Exception ex) + + // Small delay every 60 requests + if (minutesAhead > 0 && minutesAhead % 60 == 0) { - _logger.LogDebug(ex, "Failed to fetch {Filename}", filename); + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); } } + + // Then search backwards 12 hours from sync time + if (!found) + { + for (var minutesBehind = 1; minutesBehind <= 720; minutesBehind++) + { + if (cancellationToken.IsCancellationRequested) break; + + var time = syncTime.AddMinutes(-minutesBehind); + if (await TryFetchMissingTracksFile(playlistName, time, jellyfinUrl, apiKey, httpClient, cancellationToken)) + { + found = true; + break; + } + + // Small delay every 60 requests + if (minutesBehind % 60 == 0) + { + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + } + } + } + + if (!found) + { + _logger.LogWarning("Could not find missing tracks file for {Playlist} in ±12 hour window", playlistName); + } + } + + private async Task TryFetchMissingTracksFile( + string playlistName, + DateTime time, + string jellyfinUrl, + string apiKey, + HttpClient httpClient, + CancellationToken cancellationToken) + { + 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) + { + var json = await response.Content.ReadAsStringAsync(cancellationToken); + var tracks = ParseMissingTracks(json); + + if (tracks.Count > 0) + { + var cacheKey = $"spotify:missing:{playlistName}"; + await _cache.SetAsync(cacheKey, tracks, TimeSpan.FromHours(24)); + _logger.LogInformation( + "✓ Cached {Count} missing tracks for {Playlist} from {Filename}", + tracks.Count, playlistName, filename); + return true; + } + } + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Failed to fetch {Filename}", filename); + } + + return false; } private List ParseMissingTracks(string json)