namespace ErsatzTV.Core.Metadata; // #484: a media-server API client maps each item the server returned through a private projection. That // projection has TWO reasons to produce nothing, and conflating them is dangerous: // // - Skipped — a deliberate guard clause (a virtual/non-FileSystem item, a STRM file, an unsupported // item type). The item is permanently and expectedly absent from the incoming set; a // library containing one STRM file produces a Skipped on every single scan, forever. // - Failed — the projection threw and was swallowed by a `catch { LogWarning; }`. The server DID // return the item; we simply could not build it. At the reconcile step that is // indistinguishable from a deletion, so a projection regression can mass-flag healthy // items FileNotFound (which EmptyTrash then deletes permanently). // // Only Failed may suppress the reconciliation sweep (see MediaServerReconciliationGuard). Treating // Skipped as a failure would permanently disable reconciliation for any library holding a single STRM // file, so stale rows would accumulate forever — a regression, not a safe default. public readonly struct MediaServerProjectionResult { private MediaServerProjectionResult(Option item, bool isFailure) { Item = item; IsFailure = isFailure; } public Option Item { get; } /// /// True only when the projection threw. A deliberate guard-clause skip is NOT a failure. /// public bool IsFailure { get; } public static MediaServerProjectionResult Projected(T item) => new(item, false); /// /// The server returned an item we deliberately and permanently do not import (virtual item, STRM /// file, unsupported type). Expected on every scan; never suppresses a sweep. /// public static MediaServerProjectionResult Skipped() => new(Option.None, false); /// /// The projection threw and the exception was swallowed. The item exists upstream but is missing /// from the incoming set, so the sweep must not run. /// public static MediaServerProjectionResult Failed() => new(Option.None, true); public static implicit operator MediaServerProjectionResult(T item) => Projected(item); public Option ToOption() => Item; }