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.
139 lines
4.9 KiB
C#
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;
|
|
}
|
|
}
|