Fix bug where stale cache causes media player to mix up episode names
All checks were successful
🏗️ Build Plugin / build (push) Successful in 2m2s
🧪 Test Plugin / test (push) Successful in 58s

This commit is contained in:
Duncan Tourolle 2025-12-30 15:53:31 +01:00
parent 221a3f634d
commit c54221fba2
3 changed files with 30 additions and 4 deletions

View File

@ -387,10 +387,9 @@ public class JellypodChannel : IChannel, IHasCacheKey, IRequiresMediaInfoCallbac
/// <inheritdoc />
public string? GetCacheKey(string? userId)
{
// Use 5-minute time buckets for cache key
var now = DateTime.Now;
var timeBucket = new DateTime(now.Year, now.Month, now.Day, now.Hour, (now.Minute / 5) * 5, 0);
return timeBucket.ToString("yyyy-MM-dd-HH-mm", CultureInfo.InvariantCulture);
// Include database modification time so cache invalidates when podcasts/episodes change
var lastModified = _storageService.LastModified;
return lastModified.ToString("O", CultureInfo.InvariantCulture);
}
/// <inheritdoc />

View File

@ -10,6 +10,12 @@ namespace Jellyfin.Plugin.Jellypod.Services;
/// </summary>
public interface IPodcastStorageService
{
/// <summary>
/// Gets the cached last modification time (synchronous, for cache key generation).
/// Returns default if database hasn't been loaded yet.
/// </summary>
DateTime LastModified { get; }
/// <summary>
/// Gets all subscribed podcasts.
/// </summary>
@ -57,4 +63,10 @@ public interface IPodcastStorageService
/// </summary>
/// <returns>The base path for podcast storage.</returns>
string GetStoragePath();
/// <summary>
/// Gets the last time the database was modified.
/// </summary>
/// <returns>The last modification time, or null if unknown.</returns>
Task<DateTime?> GetLastModifiedAsync();
}

View File

@ -28,6 +28,7 @@ public sealed class PodcastStorageService : IPodcastStorageService, IDisposable
private readonly IApplicationPaths _applicationPaths;
private readonly SemaphoreSlim _dbLock = new(1, 1);
private PodcastDatabase? _cache;
private DateTime _lastModified;
/// <summary>
/// Initializes a new instance of the <see cref="PodcastStorageService"/> class.
@ -47,6 +48,9 @@ public sealed class PodcastStorageService : IPodcastStorageService, IDisposable
"Jellypod",
"podcasts.json");
/// <inheritdoc />
public DateTime LastModified => _lastModified;
/// <inheritdoc />
public async Task<IReadOnlyList<Podcast>> GetAllPodcastsAsync()
{
@ -180,6 +184,7 @@ public sealed class PodcastStorageService : IPodcastStorageService, IDisposable
{
_logger.LogWarning("Database file does not exist at {Path}", DatabasePath);
_cache = new PodcastDatabase();
_lastModified = DateTime.UtcNow;
return _cache;
}
@ -189,6 +194,7 @@ public sealed class PodcastStorageService : IPodcastStorageService, IDisposable
var json = await File.ReadAllTextAsync(DatabasePath).ConfigureAwait(false);
_logger.LogInformation("Read {Length} characters from database file", json.Length);
_cache = JsonSerializer.Deserialize<PodcastDatabase>(json, JsonOptions) ?? new PodcastDatabase();
_lastModified = _cache.LastSaved;
_logger.LogInformation("Loaded {Count} podcasts from database", _cache.Podcasts.Count);
return _cache;
}
@ -196,6 +202,7 @@ public sealed class PodcastStorageService : IPodcastStorageService, IDisposable
{
_logger.LogError(ex, "Failed to load podcast database, starting fresh");
_cache = new PodcastDatabase();
_lastModified = DateTime.UtcNow;
return _cache;
}
}
@ -211,6 +218,7 @@ public sealed class PodcastStorageService : IPodcastStorageService, IDisposable
}
db.LastSaved = DateTime.UtcNow;
_lastModified = db.LastSaved;
var json = JsonSerializer.Serialize(db, JsonOptions);
await File.WriteAllTextAsync(DatabasePath, json).ConfigureAwait(false);
_cache = db;
@ -261,6 +269,13 @@ public sealed class PodcastStorageService : IPodcastStorageService, IDisposable
return ".mp3";
}
/// <inheritdoc />
public async Task<DateTime?> GetLastModifiedAsync()
{
var db = await LoadDatabaseAsync().ConfigureAwait(false);
return db.LastSaved;
}
/// <inheritdoc />
public void Dispose()
{