jellyfin-srfPlay/Jellyfin.Plugin.SRFPlay/Services/ContentExpirationService.cs
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

139 lines
4.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.SRFPlay.Services;
/// <summary>
/// Service for managing content expiration.
/// </summary>
public class ContentExpirationService : IContentExpirationService
{
private readonly ILogger<ContentExpirationService> _logger;
private readonly ILibraryManager _libraryManager;
private readonly IStreamUrlResolver _streamResolver;
private readonly IMediaCompositionFetcher _compositionFetcher;
/// <summary>
/// Initializes a new instance of the <see cref="ContentExpirationService"/> class.
/// </summary>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="streamResolver">The stream URL resolver.</param>
/// <param name="compositionFetcher">The media composition fetcher.</param>
public ContentExpirationService(
ILoggerFactory loggerFactory,
ILibraryManager libraryManager,
IStreamUrlResolver streamResolver,
IMediaCompositionFetcher compositionFetcher)
{
_logger = loggerFactory.CreateLogger<ContentExpirationService>();
_libraryManager = libraryManager;
_streamResolver = streamResolver;
_compositionFetcher = compositionFetcher;
}
/// <summary>
/// Checks for expired content and removes it from the library.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The number of items removed.</returns>
public async Task<int> CheckAndRemoveExpiredContentAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Starting content expiration check");
var removedCount = 0;
try
{
// Get all items with SRF provider ID
var query = new InternalItemsQuery
{
HasAnyProviderId = new Dictionary<string, string> { { "SRF", string.Empty } },
IsVirtualItem = false
};
var items = _libraryManager.GetItemList(query);
_logger.LogDebug("Found {Count} SRF items to check for expiration", items.Count);
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
try
{
if (await IsItemExpiredAsync(item, cancellationToken).ConfigureAwait(false))
{
_logger.LogInformation("Removing expired item: {Name} (URN: {Urn})", item.Name, item.ProviderIds.GetValueOrDefault("SRF"));
// Delete the item from library
_libraryManager.DeleteItem(
item,
new DeleteOptions
{
DeleteFileLocation = false
},
false);
removedCount++;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking expiration for item: {Name}", item.Name);
}
}
_logger.LogInformation("Content expiration check completed. Removed {Count} expired items", removedCount);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during content expiration check");
}
return removedCount;
}
/// <summary>
/// Checks if an item is expired.
/// </summary>
/// <param name="item">The item to check.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>True if the item is expired.</returns>
private async Task<bool> IsItemExpiredAsync(BaseItem item, CancellationToken cancellationToken)
{
var urn = item.ProviderIds.GetValueOrDefault("SRF");
if (string.IsNullOrEmpty(urn))
{
return false;
}
var mediaComposition = await _compositionFetcher.GetMediaCompositionAsync(urn, cancellationToken).ConfigureAwait(false);
if (mediaComposition?.HasChapters != true)
{
// If we can't fetch the content, consider it expired
_logger.LogWarning("Could not fetch media composition for URN: {Urn}, treating as expired", urn);
return true;
}
var chapter = mediaComposition.ChapterList[0];
var isExpired = _streamResolver.IsContentExpired(chapter);
if (isExpired)
{
_logger.LogDebug("Item {Name} is expired (ValidTo: {ValidTo})", item.Name, chapter.ValidTo);
}
return isExpired;
}
}