Files
jRay/Jellyfin.Plugin.JRay/Controllers/TruthController.cs
T
dtourolle 19aecee646 feat(truth): schema_version 2 read path, v2 only
Replaces the v1 shape rather than accepting both. `anneal_sec` and the
top-level `sample_fps` are deleted, not zeroed; `extraction` and `cut`
blocks arrive; `scenes` become objects carrying belief and route, so a
window records how far to trust it instead of being a bare float pair.

`TruthSchema.IsSupported` is the single gate and is applied on all four
read paths — sidecar, managed store load, managed PUT, and converted
manifest. Previously only the controller checked, so the version the
plugin claimed to require and the one it would actually parse were free
to drift. Rejections name the file and the version found, so an item that
looks empty is distinguishable from one that was refused.

`ManifestConverter` carries belief, route and both provenance blocks
through: dropping them would silently downgrade every fetched manifest
against a locally extracted one.

TRACES: JR-002, JR-003 | SR-003
2026-07-31 16:23:58 +02:00

85 lines
3.3 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.JRay.Models;
using Jellyfin.Plugin.JRay.Services;
using Jellyfin.Plugin.JRay.Services.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Plugin.JRay.Controllers;
/// <summary>
/// Accepts scene-actor-extraction "truth" data pushed directly by a remote
/// extraction worker, for servers that cannot run the extraction pipeline
/// locally. See SPEC.md §2.
/// </summary>
/// <remarks>
/// The <c>schema_version</c> check refuses an unrecognised version rather than
/// guessing at its shape. It defers to <see cref="TruthSchema"/> rather than
/// holding its own constant: this used to be the only source that checked while
/// sidecar reads did not, so the version the plugin claimed to require and the
/// one it would actually parse could drift apart.
/// </remarks>
[ApiController]
[Route("Plugins/JRay/Items/{itemId}/Truth")]
[Authorize(Roles = "Administrator")]
// TRACES: JR-003, JR-009, JR-014 | SR-003
public class TruthController : ControllerBase
{
private readonly IManagedTruthStore _managedTruthStore;
private readonly ITruthDataService _truthDataService;
/// <summary>
/// Initializes a new instance of the <see cref="TruthController"/> class.
/// </summary>
/// <param name="managedTruthStore">The managed truth store.</param>
/// <param name="truthDataService">The truth data service.</param>
public TruthController(IManagedTruthStore managedTruthStore, ITruthDataService truthDataService)
{
_managedTruthStore = managedTruthStore;
_truthDataService = truthDataService;
}
/// <summary>
/// Uploads (creates or replaces) the truth data for an item.
/// </summary>
/// <param name="itemId">The Jellyfin item id.</param>
/// <param name="truth">The truth file contents.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>204 on success, or 400 if the schema version is unsupported.</returns>
[HttpPut]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> PutTruth(Guid itemId, [FromBody] TruthFile truth, CancellationToken cancellationToken)
{
if (!TruthSchema.IsSupported(truth))
{
return BadRequest(TruthSchema.DescribeRejection(truth.SchemaVersion));
}
var provenance = TruthProvenance.Local(TruthSource.Pushed, DateTime.UtcNow);
await _managedTruthStore.SaveAsync(itemId, truth, provenance, cancellationToken).ConfigureAwait(false);
_truthDataService.Invalidate(itemId);
return NoContent();
}
/// <summary>
/// Removes any managed truth data for an item. The item falls back to
/// its sidecar truth file (if any) on subsequent reads.
/// </summary>
/// <param name="itemId">The Jellyfin item id.</param>
/// <returns>204, whether or not managed data existed.</returns>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public IActionResult DeleteTruth(Guid itemId)
{
_managedTruthStore.Delete(itemId);
_truthDataService.Invalidate(itemId);
return NoContent();
}
}