using System; using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Enums; using Jellyfin.Plugin.JRay.Models; using Jellyfin.Plugin.JRay.Services; using Jellyfin.Plugin.JRay.Services.Interfaces; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Plugin.JRay.Controllers; /// /// Lets a remote extraction worker discover which library items still need /// to be processed. /// [ApiController] [Route("Plugins/JRay/Tasks")] [Authorize(Roles = "Administrator")] public class TasksController : ControllerBase { private const int DefaultLimit = 10; private const int MaxLimit = 100; private readonly ILibraryManager _libraryManager; private readonly ITruthDataService _truthDataService; private readonly IMediaPolicyStore _policyStore; /// /// Initializes a new instance of the class. /// /// The Jellyfin library manager. /// The truth data service. /// The prioritise/ignore policy store. public TasksController(ILibraryManager libraryManager, ITruthDataService truthDataService, IMediaPolicyStore policyStore) { _libraryManager = libraryManager; _truthDataService = truthDataService; _policyStore = policyStore; } /// /// Gets a random sample of movies/episodes that have no truth data yet. /// /// The maximum number of items to return (default 10, max 100). /// Up to items with no truth data, in random order. [HttpGet("Pending")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetPending([FromQuery] int limit = DefaultLimit) { var effectiveLimit = Math.Clamp(limit, 1, MaxLimit); var rules = _policyStore.GetRules(); var items = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { BaseItemKind.Movie, BaseItemKind.Episode }, IsVirtualItem = false, Recursive = true, }); // Keep only items that still need truth data, then apply the policy: // drop anything ignored, and order prioritised items ahead of the rest. // Randomise within each tier so the backlog still spreads across workers. var pending = items .Where(item => !string.IsNullOrEmpty(item.Path) && !_truthDataService.HasTruth(item.Id, item.Path)) .Select(item => new { Item = item, Action = PolicyResolver.Resolve(rules, item.Id, GetSeriesId(item), item.Genres) }) .Where(x => x.Action != PolicyAction.Ignore) .OrderByDescending(x => x.Action == PolicyAction.Prioritise) .ThenBy(_ => Random.Shared.Next()) .Take(effectiveLimit) .Select(x => new PendingExtractionItem { ItemId = x.Item.Id, Path = x.Item.Path, Name = x.Item.Name }); return Ok(pending); } /// /// Gets the series id for an episode, or for any /// other item type (so series rules only ever match episodes). /// /// The library item. /// The owning series id, or empty. internal static Guid GetSeriesId(BaseItem item) { return item is Episode episode ? episode.SeriesId : Guid.Empty; } }