mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
fix playlist, fix session sending
This commit is contained in:
@@ -63,11 +63,12 @@ public class JellyfinProxyServiceTests
|
||||
SetupMockResponse(HttpStatusCode.OK, jsonResponse, "application/json");
|
||||
|
||||
// Act
|
||||
var result = await _service.GetJsonAsync("Items");
|
||||
var (body, statusCode) = await _service.GetJsonAsync("Items");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.RootElement.TryGetProperty("Items", out var items));
|
||||
Assert.NotNull(body);
|
||||
Assert.Equal(200, statusCode);
|
||||
Assert.True(body.RootElement.TryGetProperty("Items", out var items));
|
||||
Assert.Equal(1, items.GetArrayLength());
|
||||
}
|
||||
|
||||
@@ -78,10 +79,11 @@ public class JellyfinProxyServiceTests
|
||||
SetupMockResponse(HttpStatusCode.InternalServerError, "", "text/plain");
|
||||
|
||||
// Act
|
||||
var result = await _service.GetJsonAsync("Items");
|
||||
var (body, statusCode) = await _service.GetJsonAsync("Items");
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
Assert.Null(body);
|
||||
Assert.Equal(500, statusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -207,12 +209,13 @@ public class JellyfinProxyServiceTests
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = await _service.GetItemAsync("abc-123");
|
||||
var (body, statusCode) = await _service.GetItemAsync("abc-123");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(captured);
|
||||
Assert.Contains("/Items/abc-123", captured!.RequestUri!.ToString());
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(body);
|
||||
Assert.Equal(200, statusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -2448,7 +2448,10 @@ public class JellyfinController : ControllerBase
|
||||
}
|
||||
|
||||
// Modify response if it contains Spotify playlists to update ChildCount
|
||||
if (_spotifySettings.Enabled && result.RootElement.TryGetProperty("Items", out var items))
|
||||
// Only check for Items if the response is an object (not a string or array)
|
||||
if (_spotifySettings.Enabled &&
|
||||
result.RootElement.ValueKind == JsonValueKind.Object &&
|
||||
result.RootElement.TryGetProperty("Items", out var items))
|
||||
{
|
||||
_logger.LogInformation("Response has Items property, checking for Spotify playlists to update counts");
|
||||
result = await UpdateSpotifyPlaylistCounts(result);
|
||||
@@ -2782,11 +2785,11 @@ public class JellyfinController : ControllerBase
|
||||
{
|
||||
missingTracks = await LoadMissingTracksFromFile(spotifyPlaylistName);
|
||||
|
||||
// If we loaded from file, restore to Redis
|
||||
// If we loaded from file, restore to Redis with no expiration
|
||||
if (missingTracks != null && missingTracks.Count > 0)
|
||||
{
|
||||
await _cache.SetAsync(missingTracksKey, missingTracks, TimeSpan.FromHours(24));
|
||||
_logger.LogInformation("Restored {Count} missing tracks from file cache for {Playlist}",
|
||||
await _cache.SetAsync(missingTracksKey, missingTracks, TimeSpan.FromDays(365));
|
||||
_logger.LogInformation("Restored {Count} missing tracks from file cache for {Playlist} (no expiration)",
|
||||
missingTracks.Count, spotifyPlaylistName);
|
||||
}
|
||||
}
|
||||
@@ -2981,12 +2984,9 @@ public class JellyfinController : ControllerBase
|
||||
return null;
|
||||
}
|
||||
|
||||
// No expiration check - cache persists until next Jellyfin job generates new file
|
||||
var fileAge = DateTime.UtcNow - System.IO.File.GetLastWriteTimeUtc(filePath);
|
||||
if (fileAge > TimeSpan.FromHours(24))
|
||||
{
|
||||
_logger.LogDebug("File cache for {Playlist} is too old ({Age:F1}h)", playlistName, fileAge.TotalHours);
|
||||
return null;
|
||||
}
|
||||
_logger.LogDebug("File cache for {Playlist} age: {Age:F1}h (no expiration)", playlistName, fileAge.TotalHours);
|
||||
|
||||
var json = await System.IO.File.ReadAllTextAsync(filePath);
|
||||
var tracks = JsonSerializer.Deserialize<List<allstarr.Models.Spotify.MissingTrack>>(json);
|
||||
|
||||
@@ -169,28 +169,20 @@ public class SpotifyMissingTracksFetcher : BackgroundService
|
||||
{
|
||||
var fileAge = DateTime.UtcNow - File.GetLastWriteTimeUtc(filePath);
|
||||
_logger.LogInformation(" File exists! Age: {Age:F1}h", fileAge.TotalHours);
|
||||
_logger.LogInformation(" ✓ Found file cache (age: {Age:F1}h, no expiration)", fileAge.TotalHours);
|
||||
|
||||
if (fileAge < TimeSpan.FromHours(24))
|
||||
// Load from file into Redis if not already there
|
||||
var key = $"spotify:missing:{playlistName}";
|
||||
if (!await _cache.ExistsAsync(key))
|
||||
{
|
||||
_logger.LogInformation(" ✓ Found recent file cache (age: {Age:F1}h)", fileAge.TotalHours);
|
||||
|
||||
// Load from file into Redis if not already there
|
||||
var key = $"spotify:missing:{playlistName}";
|
||||
if (!await _cache.ExistsAsync(key))
|
||||
{
|
||||
_logger.LogInformation(" Loading into Redis...");
|
||||
await LoadFromFileCache(playlistName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation(" Already in Redis");
|
||||
}
|
||||
return false;
|
||||
_logger.LogInformation(" Loading into Redis...");
|
||||
await LoadFromFileCache(playlistName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation(" File too old ({Age:F1}h > 24h), will fetch new", fileAge.TotalHours);
|
||||
_logger.LogInformation(" Already in Redis");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -234,14 +226,11 @@ public class SpotifyMissingTracksFetcher : BackgroundService
|
||||
{
|
||||
var cacheKey = $"spotify:missing:{playlistName}";
|
||||
var fileAge = DateTime.UtcNow - File.GetLastWriteTimeUtc(filePath);
|
||||
var ttl = TimeSpan.FromHours(24) - fileAge;
|
||||
|
||||
if (ttl > TimeSpan.Zero)
|
||||
{
|
||||
await _cache.SetAsync(cacheKey, tracks, ttl);
|
||||
_logger.LogInformation("Loaded {Count} tracks from file cache for {Playlist}",
|
||||
tracks.Count, playlistName);
|
||||
}
|
||||
// No expiration - cache persists until next Jellyfin job generates new file
|
||||
await _cache.SetAsync(cacheKey, tracks, TimeSpan.FromDays(365));
|
||||
_logger.LogInformation("Loaded {Count} tracks from file cache for {Playlist} (age: {Age:F1}h, no expiration)",
|
||||
tracks.Count, playlistName, fileAge.TotalHours);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -308,13 +297,26 @@ public class SpotifyMissingTracksFetcher : BackgroundService
|
||||
{
|
||||
var cacheKey = $"spotify:missing:{playlistName}";
|
||||
|
||||
if (await _cache.ExistsAsync(cacheKey))
|
||||
// Check if we have existing cache and when it was last updated
|
||||
var existingTracks = await _cache.GetAsync<List<MissingTrack>>(cacheKey);
|
||||
var existingFileTime = DateTime.MinValue;
|
||||
var filePath = GetCacheFilePath(playlistName);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
_logger.LogInformation(" ✓ Cache already exists for {Playlist}, skipping fetch", playlistName);
|
||||
return;
|
||||
existingFileTime = File.GetLastWriteTimeUtc(filePath);
|
||||
_logger.LogInformation(" Existing cache file from: {Time} ({Age:F1}h ago)",
|
||||
existingFileTime, (DateTime.UtcNow - existingFileTime).TotalHours);
|
||||
}
|
||||
|
||||
_logger.LogInformation(" No cache found, will search for missing tracks file...");
|
||||
if (existingTracks != null && existingTracks.Count > 0)
|
||||
{
|
||||
_logger.LogInformation(" Current cache has {Count} tracks, will search for newer file", existingTracks.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation(" No existing cache, will search for missing tracks file");
|
||||
}
|
||||
|
||||
var settings = _spotifySettings.Value;
|
||||
var jellyfinUrl = _jellyfinSettings.Value.Url;
|
||||
@@ -340,6 +342,7 @@ public class SpotifyMissingTracksFetcher : BackgroundService
|
||||
_logger.LogInformation(" Searching +12h forward, -24h backward from {SyncTime}", syncTime);
|
||||
|
||||
var found = false;
|
||||
DateTime? foundFileTime = null;
|
||||
|
||||
// Search forward 12 hours from sync time
|
||||
_logger.LogInformation(" Phase 1: Searching forward 12 hours from sync time...");
|
||||
@@ -348,9 +351,11 @@ public class SpotifyMissingTracksFetcher : BackgroundService
|
||||
if (cancellationToken.IsCancellationRequested) break;
|
||||
|
||||
var time = syncTime.AddMinutes(minutesAhead);
|
||||
if (await TryFetchMissingTracksFile(playlistName, time, jellyfinUrl, apiKey, httpClient, cancellationToken))
|
||||
var result = await TryFetchMissingTracksFile(playlistName, time, jellyfinUrl, apiKey, httpClient, cancellationToken, existingFileTime);
|
||||
if (result.found)
|
||||
{
|
||||
found = true;
|
||||
foundFileTime = result.fileTime;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -370,9 +375,11 @@ public class SpotifyMissingTracksFetcher : BackgroundService
|
||||
if (cancellationToken.IsCancellationRequested) break;
|
||||
|
||||
var time = syncTime.AddMinutes(-minutesBehind);
|
||||
if (await TryFetchMissingTracksFile(playlistName, time, jellyfinUrl, apiKey, httpClient, cancellationToken))
|
||||
var result = await TryFetchMissingTracksFile(playlistName, time, jellyfinUrl, apiKey, httpClient, cancellationToken, existingFileTime);
|
||||
if (result.found)
|
||||
{
|
||||
found = true;
|
||||
foundFileTime = result.fileTime;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -386,17 +393,54 @@ public class SpotifyMissingTracksFetcher : BackgroundService
|
||||
|
||||
if (!found)
|
||||
{
|
||||
_logger.LogWarning(" ✗ Could not find missing tracks file (searched +12h/-24h window)");
|
||||
_logger.LogWarning(" ✗ Could not find new missing tracks file (searched +12h/-24h window)");
|
||||
|
||||
// Keep the existing cache - don't let it expire
|
||||
if (existingTracks != null && existingTracks.Count > 0)
|
||||
{
|
||||
_logger.LogInformation(" ✓ Keeping existing cache with {Count} tracks (no expiration)", existingTracks.Count);
|
||||
// Re-save with no expiration to ensure it persists
|
||||
await _cache.SetAsync(cacheKey, existingTracks, TimeSpan.FromDays(365)); // Effectively no expiration
|
||||
}
|
||||
else if (File.Exists(filePath))
|
||||
{
|
||||
// Load from file if Redis cache is empty
|
||||
_logger.LogInformation(" 📦 Loading existing file cache to keep playlist populated");
|
||||
try
|
||||
{
|
||||
var json = await File.ReadAllTextAsync(filePath, cancellationToken);
|
||||
var tracks = JsonSerializer.Deserialize<List<MissingTrack>>(json);
|
||||
|
||||
if (tracks != null && tracks.Count > 0)
|
||||
{
|
||||
await _cache.SetAsync(cacheKey, tracks, TimeSpan.FromDays(365)); // No expiration
|
||||
_logger.LogInformation(" ✓ Loaded {Count} tracks from file cache (no expiration)", tracks.Count);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, " Failed to reload cache from file for {Playlist}", playlistName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> TryFetchMissingTracksFile(
|
||||
private async Task<(bool found, DateTime? fileTime)> TryFetchMissingTracksFile(
|
||||
string playlistName,
|
||||
DateTime time,
|
||||
string jellyfinUrl,
|
||||
string apiKey,
|
||||
HttpClient httpClient,
|
||||
CancellationToken cancellationToken)
|
||||
CancellationToken cancellationToken,
|
||||
DateTime existingFileTime)
|
||||
{
|
||||
var filename = $"{playlistName}_missing_{time:yyyy-MM-dd_HH-mm}.json";
|
||||
var url = $"{jellyfinUrl}/Viperinius.Plugin.SpotifyImport/MissingTracksFile" +
|
||||
@@ -413,16 +457,24 @@ public class SpotifyMissingTracksFetcher : BackgroundService
|
||||
|
||||
if (tracks.Count > 0)
|
||||
{
|
||||
// Check if this file is newer than what we already have
|
||||
if (time <= existingFileTime)
|
||||
{
|
||||
_logger.LogDebug(" Skipping {Filename} - not newer than existing cache", filename);
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
var cacheKey = $"spotify:missing:{playlistName}";
|
||||
|
||||
// Save to both Redis and file
|
||||
await _cache.SetAsync(cacheKey, tracks, TimeSpan.FromHours(24));
|
||||
// Save to both Redis and file with extended TTL until next job runs
|
||||
// Set to 365 days (effectively no expiration) - will be replaced when Jellyfin generates new file
|
||||
await _cache.SetAsync(cacheKey, tracks, TimeSpan.FromDays(365));
|
||||
await SaveToFileCache(playlistName, tracks);
|
||||
|
||||
_logger.LogInformation(
|
||||
"✓ Cached {Count} missing tracks for {Playlist} from {Filename}",
|
||||
"✓ Cached {Count} missing tracks for {Playlist} from {Filename} (no expiration until next Jellyfin job)",
|
||||
tracks.Count, playlistName, filename);
|
||||
return true;
|
||||
return (true, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -431,7 +483,7 @@ public class SpotifyMissingTracksFetcher : BackgroundService
|
||||
_logger.LogDebug(ex, "Failed to fetch {Filename}", filename);
|
||||
}
|
||||
|
||||
return false;
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
private List<MissingTrack> ParseMissingTracks(string json)
|
||||
|
||||
Reference in New Issue
Block a user