Quiblo wiki

Architecture

Metadata and artwork

Two optional third-party features, and the rules that keep them from becoming a liability.

# Why both are off by default

The metadata service and the channel-logo index are the only third-party hosts the app ever contacts. Both are off unless the user turns them on, and that is the "never phones home" invariant rather than a UI preference: a clean install talks to nothing but the hosts the user typed.

Our own releases page is the one host that is on by default, and it is the exception that proves the rule rather than a hole in it: it is our page, the request is for a public file, nothing about the device or the viewer is sent, and one switch stops it entirely. Quiblo is installed by sideload with no store behind it, so a build that cannot say it is out of date is a build whose security fixes never arrive. See Checking for updates.

# Film and series information

Requires the user's own API key. The service rate-limits per key, and the key belongs to them — being wasteful with it is being wasteful with something of theirs. So:

  • Requests are made per tile that has been on screen, not per category opened. A category can hold thousands of titles, and asking about all of them the moment it opens spends the user's limit on titles they scrolled past.
  • Answers are cached in the database, across launches — including "no match", because a negative answer is an answer.
  • A record notes whether only the search step ran. A poster tile needs a score; a detail screen needs everything. Recording which was fetched lets a tile be satisfied by one request and a detail screen upgrade the same row rather than duplicate it.
  • There is no batch endpoint, so the per-title shape is forced rather than chosen.
  • Every request passes a token bucket on the client, and a refusal is never cached.

# Describing a whole catalogue at once

The per-tile shape above is right for browsing and wrong for one thing: the genre filter, which can only offer what the cache already holds. A viewer who has looked at forty films gets a filter that knows about forty films.

So there is a scanner that walks the catalogue and asks about everything, driven from either settings screen. Its design is mostly a list of things it must not do.

  • It subtracts what is cached first, including the "no match" rows, and only then knows its total. That is why the progress bar reports no fraction while it is preparing: a bar drawn against a total of zero sits at one end or the other and means neither.
  • Four workers, and no throttle of its own. Concurrency here is only about hiding latency; the pacing is the client's bucket, which every worker waits on. Giving the scanner a second rate limit would be two things to keep in agreement, and the account blocks came from believing a concurrency cap was a rate limit.
  • One refusal stops everything. A volatile flag is checked by each worker before it asks, rather than at collection, so a rate limit stops the requests themselves rather than merely stopping the counting.
  • Everything already fetched stays fetched. Stopped, cancelled or finished, the cache keeps what it got and the reported state keeps the fraction it reached — starting again resumes from there, which is only true because the work list is computed by subtraction.

The state it exposes distinguishes finished, stopped and cancelled, and a stopped scan carries why: rate limited, key rejected, or unavailable. Those three call for different actions from the viewer — wait, fix the key, try later — and collapsing them into "something went wrong" would leave the one that is actionable indistinguishable from the two that are not.

That reason is a restatement of the metadata client's own refusal type rather than a reuse of it. A settings screen importing the TMDB module to render a message would be the first crack in the rule that features talk to :core:data and nothing else.

# Channel logos

The opposite shape, because the data is. The reference list is one large static file, so it is downloaded once, stored, and queried locally — a request per channel is not an option the API offers, and downloading it once per session would be several megabytes to answer a question about a logo.

It gets its own table rather than a column on the channel row, for the same reason favourites do: a column would be destroyed on every refresh, and rebuilding it would mean re-downloading, which is the one thing this cache exists to prevent.

# Provider artwork always wins

Neither source is ever preferred over the provider's own artwork. A panel's cover is the cover for the thing it is serving, and second-guessing it is how a grid ends up showing the wrong film's poster.

Both fill the same gap and are held in one map rather than two, because the consumer's question is a single question — "is there anything to show in this empty frame?" — and a tile has no use for the distinction.