Duncan Tourolle 198fc4c58d
All checks were successful
🚀 Release Plugin / build-and-release (push) Successful in 2m51s
🏗️ Build Plugin / build (push) Successful in 2m50s
🧪 Test Plugin / test (push) Successful in 1m20s
more consolidation
2025-12-07 13:29:13 +01:00

215 lines
8.1 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.Services.Interfaces;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.SRFPlay.Services;
/// <summary>
/// Service for refreshing content from SRF API.
/// </summary>
public class ContentRefreshService : IContentRefreshService
{
private readonly ILogger<ContentRefreshService> _logger;
private readonly ISRFApiClientFactory _apiClientFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ContentRefreshService"/> class.
/// </summary>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="apiClientFactory">The API client factory.</param>
public ContentRefreshService(
ILoggerFactory loggerFactory,
ISRFApiClientFactory apiClientFactory)
{
_logger = loggerFactory.CreateLogger<ContentRefreshService>();
_apiClientFactory = apiClientFactory;
}
/// <summary>
/// Refreshes latest content from SRF API using Play v3.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>List of URNs for new content.</returns>
public async Task<List<string>> RefreshLatestContentAsync(CancellationToken cancellationToken)
{
var config = Plugin.Instance?.Configuration;
if (config == null || !config.EnableLatestContent)
{
_logger.LogDebug("Latest content refresh is disabled");
return new List<string>();
}
return await FetchVideosFromShowsAsync(
config.BusinessUnit.ToString().ToLowerInvariant(),
minEpisodeCount: 0,
maxShows: 20,
videosPerShow: 1,
contentType: "latest",
cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Refreshes trending content from SRF API using Play v3.
/// Gets videos from shows with the most episodes.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>List of URNs for trending content.</returns>
public async Task<List<string>> RefreshTrendingContentAsync(CancellationToken cancellationToken)
{
var config = Plugin.Instance?.Configuration;
if (config == null || !config.EnableTrendingContent)
{
_logger.LogDebug("Trending content refresh is disabled");
return new List<string>();
}
return await FetchVideosFromShowsAsync(
config.BusinessUnit.ToString().ToLowerInvariant(),
minEpisodeCount: 10,
maxShows: 15,
videosPerShow: 2,
contentType: "trending",
cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Fetches videos from shows based on filter criteria.
/// </summary>
private async Task<List<string>> FetchVideosFromShowsAsync(
string businessUnit,
int minEpisodeCount,
int maxShows,
int videosPerShow,
string contentType,
CancellationToken cancellationToken)
{
var urns = new List<string>();
try
{
_logger.LogInformation("Refreshing {ContentType} content for business unit: {BusinessUnit}", contentType, businessUnit);
using var apiClient = _apiClientFactory.CreateClient();
var shows = await apiClient.GetAllShowsAsync(businessUnit, cancellationToken).ConfigureAwait(false);
if (shows == null || shows.Count == 0)
{
_logger.LogWarning("No shows found for business unit: {BusinessUnit}", businessUnit);
return urns;
}
_logger.LogInformation("Found {Count} shows, fetching {ContentType} content", shows.Count, contentType);
var filteredShows = shows
.Where(s => s.NumberOfEpisodes > minEpisodeCount)
.OrderByDescending(s => s.NumberOfEpisodes)
.Take(maxShows)
.ToList();
var now = DateTime.UtcNow;
foreach (var show in filteredShows)
{
if (show.Id == null || cancellationToken.IsCancellationRequested)
{
continue;
}
try
{
var videos = await apiClient.GetVideosForShowAsync(businessUnit, show.Id, cancellationToken).ConfigureAwait(false);
if (videos == null || videos.Count == 0)
{
_logger.LogDebug("Show {Show} ({ShowId}): No videos returned from API", show.Title, show.Id);
continue;
}
_logger.LogDebug("Show {Show} ({ShowId}): Found {Count} videos", show.Title, show.Id, videos.Count);
// Filter to videos that are actually published (validFrom in the past)
var publishedVideos = videos
.Where(v => v.ValidFrom == null || v.ValidFrom.Value.ToUniversalTime() <= now)
.OrderByDescending(v => v.Date)
.Take(videosPerShow)
.ToList();
foreach (var video in publishedVideos)
{
if (video.Urn != null)
{
urns.Add(video.Urn);
_logger.LogDebug(
"Added {ContentType} video from show {Show}: {Title} (URN: {Urn})",
contentType,
show.Title,
video.Title,
video.Urn);
}
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error fetching videos for show {ShowId}", show.Id);
}
}
_logger.LogInformation("Refreshed {Count} {ContentType} content items from {ShowCount} shows", urns.Count, contentType, filteredShows.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error refreshing {ContentType} content", contentType);
}
return urns;
}
/// <summary>
/// Refreshes all content (latest and trending).
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Tuple with counts of latest and trending items.</returns>
public async Task<(int LatestCount, int TrendingCount)> RefreshAllContentAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Starting full content refresh");
var latestUrns = await RefreshLatestContentAsync(cancellationToken).ConfigureAwait(false);
var trendingUrns = await RefreshTrendingContentAsync(cancellationToken).ConfigureAwait(false);
var latestCount = latestUrns.Count;
var trendingCount = trendingUrns.Count;
_logger.LogInformation(
"Content refresh completed. Latest: {LatestCount}, Trending: {TrendingCount}",
latestCount,
trendingCount);
return (latestCount, trendingCount);
}
/// <summary>
/// Gets content recommendations (combines latest and trending).
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>List of recommended URNs.</returns>
public async Task<List<string>> GetRecommendedContentAsync(CancellationToken cancellationToken)
{
var recommendations = new HashSet<string>();
var latestUrns = await RefreshLatestContentAsync(cancellationToken).ConfigureAwait(false);
var trendingUrns = await RefreshTrendingContentAsync(cancellationToken).ConfigureAwait(false);
foreach (var urn in latestUrns.Concat(trendingUrns))
{
recommendations.Add(urn);
}
_logger.LogInformation("Generated {Count} content recommendations", recommendations.Count);
return recommendations.ToList();
}
}