source_utils_mediaSources.bs

import "pkg:/source/api/ApiClient.bs"
import "pkg:/source/api/apiPool.bs"
import "pkg:/source/utils/misc.bs"

' Fetching a set of items' MediaSources by id.
'
' Two features ask the same question — "which version ids belong to these items?" — and both
' reach it from a Task thread holding a list of ids:
'
'   * episodeQueue.collapseCopies(), choosing one copy per episode on a pre-12 server, where
'     the server lists every file of an episode as its own episode
'   * the display-progress correction (versionResume's DISPLAY section), mapping a grouped
'     tile back to whichever of its versions is in progress
'
' It lives here rather than inside either of them because neither owns it. Fields=MediaSources
' is the expensive field on any item query, so asking for it BY ID — rather than for a whole
' page — is what keeps the cost tied to the few items that need it.
namespace mediaSources

  ' How many ids one request names. Each costs 35 bytes of URL (32 hex + an encoded comma), so
  ' 100 keeps a request near 3.5 KB — well inside common request-line limits.
  const IDS_PER_REQUEST = 100

  ' idChunks: The ids split into runs of at most `size`, in order — one request each.
  '
  ' @param {object} ids - array of strings
  ' @param {integer} size - the most ids in one run (at least 1)
  ' @return {object} array of arrays
  function idChunks(ids as object, size as integer) as object
    chunks = []
    if size < 1 then size = 1
    chunk = []
    for each id in ids
      chunk.push(id)
      if chunk.count() = size
        chunks.push(chunk)
        chunk = []
      end if
    end for
    if chunk.count() > 0 then chunks.push(chunk)
    return chunks
  end function

  ' byIds: Each item's MediaSources, by item id.
  '
  ' The imperative half — runs on a Task thread, blocking, IDS_PER_REQUEST ids per request. An
  ' id the server does not answer for is simply ABSENT from the result; every caller reads that
  ' as "no versions known" rather than as an error, so a partial answer degrades instead of
  ' failing the screen.
  '
  ' @param {object} ids - array of item id strings
  ' @param {string} requestId - for the pool
  ' @return {object} AA of item id -> MediaSources array
  function byIds(ids as object, requestId as string) as object
    sourcesById = {}
    if not isValidAndNotEmpty(ids) then return sourcesById

    for each chunk in mediaSources.idChunks(ids, mediaSources.IDS_PER_REQUEST)
      data = fetchJson(GetApi().BuildGetItemsByQueryRequest({ Ids: chunk.join(","), Fields: "MediaSources", EnableTotalRecordCount: false }), requestId)
      if isValid(data) and isValid(data.Items)
        for each fetched in data.Items
          if isValidAndNotEmpty(fetched.Id) then sourcesById[fetched.Id] = fetched.MediaSources
        end for
      end if
    end for

    return sourcesById
  end function

end namespace