Satisfies JRay-public-server UR-007. Servers are tried in configured order and the first result clearing the configured tier wins; first-match rather than best-match because querying every server for every item multiplies egress and leaks the library to more parties, and the ordering already encodes which source the admin prefers. Every server is untrusted, including the pre-configured community one, so a fetched manifest is re-validated against the same rules the server applies on upload: envelope version refused if unknown, identifiers format-checked, windows bounds-checked against the *local* file's runtime, belief bounded to [0, 1], control and bidi characters refused in names. Responses are capped while streaming rather than after buffering, since a hostile server can declare any Content-Length it likes. HTTPS is required away from loopback. A failing server is skipped with exponential backoff so one dead server cannot stall a sweep. The audio-tier offset is applied once, at store time, so stored truth is always in the local file's own timebase and no read path needs offset awareness. Windows are shifted, never reshaped — merging adjacent ones would answer "was a face visible" rather than "was the actor present" (SR-002). Also records why there is no `exact` tier, which was missing and led me to re-add one. The file-hash tier is withdrawn on legal grounds: a TMDB id discloses "some copy of this film", but an OpenSubtitles hash discloses "this exact release", which turns a catalogue lookup into a release-identification service and a server's database into a mapping from file fingerprints to the instances holding them. The reason now lives on MatchTier and in SPEC.md §JR-036, `TitleQuery` has no VideoHash property so there is nothing to send, and a test asserts the enum has no Exact member — the spec had still listed `exact` as a configurable tier, which is what made the removal look like an oversight. 42 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> TRACES: JR-025, JR-027, JR-028, JR-029, JR-030, JR-031, JR-036, JR-037 | PR-005, PR-006
130 lines
5.0 KiB
C#
130 lines
5.0 KiB
C#
namespace Jellyfin.Plugin.JRay.Configuration;
|
||
|
||
/// <summary>
|
||
/// How far a configured manifest server is trusted. See the JRay public server
|
||
/// specification, §9 "Trusting third-party servers".
|
||
/// </summary>
|
||
public enum ServerTrustLevel
|
||
{
|
||
/// <summary>
|
||
/// Accept manifests, but never contribute to this server and never send
|
||
/// library inventory beyond the single item being queried. The default for
|
||
/// user-added servers.
|
||
/// </summary>
|
||
FetchOnly = 0,
|
||
|
||
/// <summary>
|
||
/// Eligible to contribute to, subject to <see cref="ManifestServer.AllowContribute"/>.
|
||
/// </summary>
|
||
Full = 1,
|
||
}
|
||
|
||
/// <summary>
|
||
/// The minimum cut-match tier a fetched manifest must reach before it is stored.
|
||
/// See the public server specification, §3 "Cut matching".
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Every tier is a claim about a <em>cut</em>, never about a copy. There is no
|
||
/// file-level tier, and the plugin sends no <c>video_hash</c>.
|
||
/// <para>
|
||
/// <b>The <c>Exact</c> tier was withdrawn for legal reasons; do not re-add it
|
||
/// without an explicit, recorded agreement.</b> It keyed on the OpenSubtitles
|
||
/// file hash, which identifies the individual encode a user holds rather than
|
||
/// the edit the timings describe. A TMDB id discloses "some copy of this film";
|
||
/// a file hash discloses "<em>this exact release</em>", which turns a catalogue
|
||
/// lookup into a release-identification service and turns the server's database
|
||
/// into a mapping from file fingerprints to the instances holding them. That is
|
||
/// a far more specific disclosure than PR-005 permits, and a dataset no
|
||
/// volunteer operator should be holding.
|
||
/// </para>
|
||
/// <para>
|
||
/// The audio signature is the deliberate replacement: derived from content, it
|
||
/// identifies the <em>cut</em>, so two different encodes of the same edit agree.
|
||
/// It answers the question the exchange needs — "do these timings apply to this
|
||
/// media?" — without answering the one it must not. <c>Audio</c> is therefore
|
||
/// the top tier here.
|
||
/// </para>
|
||
/// </remarks>
|
||
public enum MatchTier
|
||
{
|
||
/// <summary>Audio 0.60–0.85, or runtimes within ±30s. Surfaced as a caveat in the UI.</summary>
|
||
Loose = 0,
|
||
|
||
/// <summary>Runtimes within ±2s.</summary>
|
||
Runtime = 1,
|
||
|
||
/// <summary>Audio signature score ≥ 0.85; may carry a non-zero offset. The top tier.</summary>
|
||
Audio = 2,
|
||
}
|
||
|
||
/// <summary>
|
||
/// One entry in the ordered list of manifest servers the plugin queries
|
||
/// (public server specification, §9 "Multiple servers").
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// The list is ordered because order *is* the user's trust ranking, made
|
||
/// explicit: for a fetch, servers are tried in order and the first acceptable
|
||
/// result wins. Querying every server for every item would multiply egress and
|
||
/// leak the library to more parties.
|
||
///
|
||
/// <c>FetchOnly</c> is the default for user-added servers. Adding a third-party
|
||
/// server means trusting its operator not to serve deliberately wrong actor
|
||
/// data — the client-side controls bound the damage to bad overlay content,
|
||
/// they cannot make wrong data right.
|
||
/// </remarks>
|
||
// TRACES: JR-025 | PR-005, PR-006
|
||
public class ManifestServer
|
||
{
|
||
/// <summary>
|
||
/// Initializes a new instance of the <see cref="ManifestServer"/> class.
|
||
/// </summary>
|
||
public ManifestServer()
|
||
{
|
||
Url = string.Empty;
|
||
Name = string.Empty;
|
||
Token = string.Empty;
|
||
Enabled = false;
|
||
AllowContribute = false;
|
||
TrustLevel = ServerTrustLevel.FetchOnly;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the base URL of the server, e.g. "https://jray.tourolle.paris".
|
||
/// HTTPS is required for non-loopback servers: a plaintext server would let
|
||
/// any network intermediary rewrite actor overlays.
|
||
/// </summary>
|
||
public string Url { get; set; }
|
||
|
||
/// <summary>
|
||
/// Gets or sets the display label shown in the configuration page.
|
||
/// </summary>
|
||
public string Name { get; set; }
|
||
|
||
/// <summary>
|
||
/// Gets or sets the API token used to contribute manifests. Optional —
|
||
/// required only to contribute, never to fetch. This is an anonymous bearer
|
||
/// capability rather than an account (public server specification, §5a).
|
||
/// </summary>
|
||
public string Token { get; set; }
|
||
|
||
/// <summary>
|
||
/// Gets or sets a value indicating whether this server is queried at all.
|
||
/// Lets an admin disable an entry without deleting it and losing its token.
|
||
/// </summary>
|
||
public bool Enabled { get; set; }
|
||
|
||
/// <summary>
|
||
/// Gets or sets a value indicating whether locally generated manifests may be
|
||
/// contributed to this server. Independent of fetching, and off by default:
|
||
/// contribution is never fanned out, because broadcasting uploads to every
|
||
/// configured server would multiply privacy exposure without the user
|
||
/// intending it.
|
||
/// </summary>
|
||
public bool AllowContribute { get; set; }
|
||
|
||
/// <summary>
|
||
/// Gets or sets how far this server is trusted.
|
||
/// </summary>
|
||
public ServerTrustLevel TrustLevel { get; set; }
|
||
}
|