source_utils_serverCapabilities.bs

' Server capabilities that depend on something INSTALLED on the server — a plugin —
' rather than on the server's version.
'
' Version-gated features read m.global.server.apiVersion; these cannot, because two
' servers on the same version differ by what the admin installed. A feature that
' needs a plugin should be hidden when the plugin is absent, not offered and then
' fail empty.
'
' THE PATTERN, for the next plugin-dependent feature:
'   * One field per capability on JellyfinServer, as a THREE-state string —
'     unknown / available / unavailable. session.server.Delete() resets every field
'     on a server switch, so an answer can never outlive the server it describes.
'   * Checked once per sign-in (Home's first run — Home is remounted on every
'     sign-in), and again by a consumer that finds it still unknown.
'   * A FAILED check is not recorded, so it stays unknown and is retried.
'   * What a consumer does with a failed check is decided PER CAPABILITY, never here.
'     Ask what happens when a user reaches the feature and the plugin is not there:
'     fail OPEN (offer it) only when that degrades safely and explains itself; fail
'     CLOSED when it would error confusingly, silently not work, or do something
'     that cannot be undone. Put that decision, and its reason, beside the
'     capability — see shouldOfferSubtitleSearch().
'
' Not every capability fits: Quick Connect's probe runs before login and needs no
' auth, and the JellyRock companion plugin is re-probed on each reconnect by
' RemoteControlTask on a Task thread. Both are correctly placed where they are.

namespace serverCapabilities

  const STATUS_UNKNOWN = ""
  const STATUS_AVAILABLE = "available"
  const STATUS_UNAVAILABLE = "unavailable"

  ' subtitleProviderStatusFrom: What a /Libraries/AvailableOptions reply says about
  ' subtitle providers.
  '
  ' SubtitleFetchers lists every installed subtitle provider plugin by name. It
  ' cannot tell whether a provider has CREDENTIALS — an installed but unconfigured
  ' provider counts as available, and the panel's empty-state message covers that.
  '
  ' @param {object} res - fetchAsync result { ok, json }
  ' @return {string} STATUS_AVAILABLE / STATUS_UNAVAILABLE, or STATUS_UNKNOWN when
  '                  the reply does not answer the question (non-ok, malformed)
  function subtitleProviderStatusFrom(res as object) as string
    if not isValid(res) or res.ok <> true then return serverCapabilities.STATUS_UNKNOWN
    if not isValid(res.json) then return serverCapabilities.STATUS_UNKNOWN

    fetchers = res.json.SubtitleFetchers
    if type(fetchers) <> "roArray" then return serverCapabilities.STATUS_UNKNOWN
    if fetchers.count() > 0 then return serverCapabilities.STATUS_AVAILABLE
    return serverCapabilities.STATUS_UNAVAILABLE
  end function

  ' recordSubtitleProviderStatus: Store a reply's answer on m.global.server.
  '
  ' An unknown answer is NOT stored, so a failed check is retried by the next
  ' consumer instead of being remembered for the rest of the session.
  '
  ' @param {object} res - fetchAsync result { ok, json }
  ' @return {string} the status the reply gave, stored or not
  function recordSubtitleProviderStatus(res as object) as string
    status = serverCapabilities.subtitleProviderStatusFrom(res)
    if status <> serverCapabilities.STATUS_UNKNOWN then m.global.server.subtitleProviderStatus = status
    return status
  end function

  ' shouldOfferSubtitleSearch: Whether to offer Manage Subtitles once the provider
  ' check has settled.
  '
  ' This capability FAILS OPEN: only a definite "unavailable" hides the button, and
  ' an unknown result here (the check failed) offers it. Safe for this feature
  ' specifically — a search without a provider returns an empty success, the panel
  ' says so and points at provider setup, and nothing is changed on the server. A
  ' transport error is not evidence the plugin is missing, so hiding a working
  ' feature on a network blip would be the worse outcome. Not a default for other
  ' capabilities; see the header.
  '
  ' @param {string} status - the status the check produced
  ' @return {boolean}
  function shouldOfferSubtitleSearch(status as string) as boolean
    return status <> serverCapabilities.STATUS_UNAVAILABLE
  end function

end namespace