Fix Spotify missing tracks search order: forward 12h then backward 12h

This commit is contained in:
2026-01-31 21:19:39 -05:00
parent bb976fed4f
commit 7e0ea501fc

View File

@@ -146,12 +146,13 @@ public class SpotifyMissingTracksFetcher : BackgroundService
.AddMinutes(settings.SyncStartMinute); .AddMinutes(settings.SyncStartMinute);
var syncEnd = syncStart.AddHours(settings.SyncWindowHours); var syncEnd = syncStart.AddHours(settings.SyncWindowHours);
if (now < syncStart || now > syncEnd) // Only run after the sync window has passed
if (now < syncEnd)
{ {
return; 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) foreach (var kvp in _playlistIdToName)
{ {
@@ -175,46 +176,106 @@ public class SpotifyMissingTracksFetcher : BackgroundService
var jellyfinUrl = _jellyfinSettings.Value.Url; var jellyfinUrl = _jellyfinSettings.Value.Url;
var apiKey = _jellyfinSettings.Value.ApiKey; var apiKey = _jellyfinSettings.Value.ApiKey;
var httpClient = _httpClientFactory.CreateClient(); 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) .AddHours(settings.SyncStartHour)
.AddMinutes(settings.SyncStartMinute); .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; if (cancellationToken.IsCancellationRequested) break;
var filename = $"{playlistName}_missing_{time:yyyy-MM-dd_HH-mm}.json"; var time = syncTime.AddMinutes(minutesAhead);
var url = $"{jellyfinUrl}/Viperinius.Plugin.SpotifyImport/MissingTracksFile" + if (await TryFetchMissingTracksFile(playlistName, time, jellyfinUrl, apiKey, httpClient, cancellationToken))
$"?name={Uri.EscapeDataString(filename)}&api_key={apiKey}";
try
{ {
_logger.LogDebug("Trying {Filename}", filename); found = true;
var response = await httpClient.GetAsync(url, cancellationToken); break;
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;
}
}
} }
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<bool> 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<MissingTrack> ParseMissingTracks(string json) private List<MissingTrack> ParseMissingTracks(string json)