215 lines
8.1 KiB
C#
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();
|
|
}
|
|
}
|