Duncan Tourolle 7c76402a4a
Some checks failed
🏗️ Build Plugin / build (push) Failing after 9s
🧪 Test Plugin / test (push) Successful in 1m28s
🚀 Release Plugin / build-and-release (push) Failing after 5s
Clean up dead code, consolidate duplication, fix redundancies
Remove 9 dead methods, 6 unused constants, and redundant
ReaderWriterLockSlim from MetadataCache. Consolidate repeated
patterns into HasChapters, IsPlayable, and ToLowerString helpers.
Extract shared API methods in SRFApiClient. Move variant manifest
rewriting from controller to StreamProxyService. Make Auto quality
distinct from HD. Update README architecture section.
2026-02-28 11:34:45 +01:00

118 lines
3.7 KiB
C#

using System;
using System.Collections.Concurrent;
using Jellyfin.Plugin.SRFPlay.Api.Models;
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.SRFPlay.Services;
/// <summary>
/// Service for caching metadata from SRF API.
/// </summary>
public sealed class MetadataCache : IMetadataCache
{
private readonly ILogger<MetadataCache> _logger;
private readonly ConcurrentDictionary<string, CacheEntry<MediaComposition>> _mediaCompositionCache;
/// <summary>
/// Initializes a new instance of the <see cref="MetadataCache"/> class.
/// </summary>
/// <param name="logger">The logger instance.</param>
public MetadataCache(ILogger<MetadataCache> logger)
{
_logger = logger;
_mediaCompositionCache = new ConcurrentDictionary<string, CacheEntry<MediaComposition>>();
}
/// <summary>
/// Gets cached media composition by URN.
/// </summary>
/// <param name="urn">The URN.</param>
/// <param name="cacheDurationMinutes">The cache duration in minutes.</param>
/// <returns>The cached media composition, or null if not found or expired.</returns>
public MediaComposition? GetMediaComposition(string urn, int cacheDurationMinutes)
{
if (string.IsNullOrEmpty(urn))
{
return null;
}
if (_mediaCompositionCache.TryGetValue(urn, out var entry))
{
if (entry.IsValid(cacheDurationMinutes))
{
_logger.LogDebug("Cache hit for URN: {Urn}", urn);
return entry.Value;
}
_logger.LogDebug("Cache entry expired for URN: {Urn}", urn);
}
return null;
}
/// <summary>
/// Sets media composition in cache.
/// </summary>
/// <param name="urn">The URN.</param>
/// <param name="mediaComposition">The media composition to cache.</param>
public void SetMediaComposition(string urn, MediaComposition mediaComposition)
{
if (string.IsNullOrEmpty(urn) || mediaComposition == null)
{
return;
}
var entry = new CacheEntry<MediaComposition>(mediaComposition);
_mediaCompositionCache.AddOrUpdate(urn, entry, (key, oldValue) => entry);
_logger.LogDebug("Cached media composition for URN: {Urn}", urn);
}
/// <summary>
/// Clears all cached data.
/// </summary>
public void Clear()
{
_mediaCompositionCache.Clear();
_logger.LogInformation("Cleared metadata cache");
}
/// <summary>
/// Represents a cached entry with timestamp.
/// </summary>
/// <typeparam name="T">The type of cached value.</typeparam>
private sealed class CacheEntry<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="CacheEntry{T}"/> class.
/// </summary>
/// <param name="value">The value to cache.</param>
public CacheEntry(T value)
{
Value = value;
Timestamp = DateTime.UtcNow;
}
/// <summary>
/// Gets the cached value.
/// </summary>
public T Value { get; }
/// <summary>
/// Gets the timestamp when the entry was created.
/// </summary>
public DateTime Timestamp { get; }
/// <summary>
/// Checks if the cache entry is still valid.
/// </summary>
/// <param name="cacheDurationMinutes">The cache duration in minutes.</param>
/// <returns>True if the entry is still valid.</returns>
public bool IsValid(int cacheDurationMinutes)
{
var expirationTime = Timestamp.AddMinutes(cacheDurationMinutes);
return DateTime.UtcNow < expirationTime;
}
}
}