129 lines
5.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.SRFPlay.Api;
using Jellyfin.Plugin.SRFPlay.Api.Models;
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.SRFPlay.Services;
/// <summary>
/// Service for managing topic/category data and filtering.
/// </summary>
public class CategoryService : ICategoryService
{
private readonly ILogger<CategoryService> _logger;
private readonly ISRFApiClientFactory _apiClientFactory;
private readonly TimeSpan _topicsCacheDuration = TimeSpan.FromHours(24);
// Cache topics per business unit. A single shared cache would return one unit's
// topics (e.g. German SRF) even after the user switched to another (e.g. French RTS).
private readonly Dictionary<string, (Dictionary<string, PlayV3Topic> Topics, DateTime Expiry)> _topicsCacheByUnit = new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Initializes a new instance of the <see cref="CategoryService"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="apiClientFactory">The API client factory.</param>
public CategoryService(ILogger<CategoryService> logger, ISRFApiClientFactory apiClientFactory)
{
_logger = logger;
_apiClientFactory = apiClientFactory;
}
/// <inheritdoc />
public async Task<List<PlayV3Topic>> GetTopicsAsync(string businessUnit, CancellationToken cancellationToken = default)
{
// Return cached topics for this business unit if still valid
if (_topicsCacheByUnit.TryGetValue(businessUnit, out var cached) && DateTime.UtcNow < cached.Expiry)
{
_logger.LogDebug("Returning cached topics for business unit: {BusinessUnit}", businessUnit);
return cached.Topics.Values.ToList();
}
_logger.LogInformation("Fetching topics for business unit: {BusinessUnit}", businessUnit);
using var apiClient = _apiClientFactory.CreateClient();
var topics = await apiClient.GetAllTopicsAsync(businessUnit, cancellationToken).ConfigureAwait(false);
if (topics != null && topics.Count > 0)
{
// Cache topics by ID for quick lookups, scoped to this business unit
var byId = topics
.Where(t => !string.IsNullOrEmpty(t.Id))
.ToDictionary(t => t.Id!, t => t);
_topicsCacheByUnit[businessUnit] = (byId, DateTime.UtcNow.Add(_topicsCacheDuration));
_logger.LogInformation("Cached {Count} topics for business unit: {BusinessUnit}", byId.Count, businessUnit);
}
return topics ?? new List<PlayV3Topic>();
}
/// <inheritdoc />
public async Task<PlayV3Topic?> GetTopicByIdAsync(string topicId, string businessUnit, CancellationToken cancellationToken = default)
{
// Ensure topics are loaded for this business unit
if (!_topicsCacheByUnit.TryGetValue(businessUnit, out var cached) || DateTime.UtcNow >= cached.Expiry)
{
await GetTopicsAsync(businessUnit, cancellationToken).ConfigureAwait(false);
_topicsCacheByUnit.TryGetValue(businessUnit, out cached);
}
return cached.Topics?.GetValueOrDefault(topicId);
}
/// <inheritdoc />
public async Task<List<PlayV3Show>> GetShowsByTopicAsync(
string topicId,
string businessUnit,
int maxResults = 50,
CancellationToken cancellationToken = default)
{
using var apiClient = _apiClientFactory.CreateClient();
var allShows = await apiClient.GetAllShowsAsync(businessUnit, cancellationToken).ConfigureAwait(false);
if (allShows == null || allShows.Count == 0)
{
_logger.LogWarning("No shows available for business unit: {BusinessUnit}", businessUnit);
return new List<PlayV3Show>();
}
var filteredShows = FilterShowsByTopic(allShows, topicId)
.Where(s => s.NumberOfEpisodes > 0)
.OrderByDescending(s => s.NumberOfEpisodes)
.Take(maxResults)
.ToList();
_logger.LogDebug("Found {Count} shows for topic {TopicId}", filteredShows.Count, topicId);
return filteredShows;
}
/// <inheritdoc />
public void ClearCache()
{
_topicsCacheByUnit.Clear();
_logger.LogInformation("Topics cache cleared");
}
/// <summary>
/// Filters shows by topic ID.
/// </summary>
/// <param name="shows">The shows to filter.</param>
/// <param name="topicId">The topic ID to filter by.</param>
/// <returns>Filtered list of shows.</returns>
private static IReadOnlyList<PlayV3Show> FilterShowsByTopic(IReadOnlyList<PlayV3Show> shows, string topicId)
{
if (string.IsNullOrEmpty(topicId))
{
return shows;
}
return shows
.Where(s => s.TopicList != null && s.TopicList.Contains(topicId))
.ToList();
}
}