feat(explore): refuse 0.6 GB on someone's mobile data
CI / check (push) Canceled after 0s
CI / e2e (push) Canceled after 0s
Search index maintenance / maintain-index (push) Canceled after 0s
Build & publish Arch package / arch-package (push) Successful in 2m30s

Plan 016 B4. The catalog artifact is about 0.6 GB and the app fetched it
with no awareness of the connection: on a desktop that is a minute of
bandwidth, on a phone it can be a month's allowance. It is now skipped on
a cellular connection unless `AllowMeteredCatalogDownload` is on, with
the toggle in Settings' Search Index section, where the text explaining
what the catalog is already lives.

The file layout is dictated by the cgo rule rather than by taste.
`explore` is imported by `cmd/indexbuild`, which builds with
CGO_ENABLED=0 and must not link Wails, so `netpolicy.go` holds the policy
and the JSON parsing -- tested on every platform -- and the single
platform call is a closure injected from `app.go`, which already names
`application` legitimately.

Three rules in it are load-bearing. An unknown answer is not a metered
one: only mobile answers at all, and treating silence as metered would
have disabled the download for every desktop user in the world. Cellular
is the only signal available, because the runtime reports
`wifi|cellular|ethernet|none` and no metered flag -- so a metered Wi-Fi
cannot be detected and is not refused, which is documented rather than
implied. And the gate runs before the first status write, so declining is
a no-op instead of a job in the indicator and an error tier to dismiss.

Two corrections to the plan while implementing it: the portable API is
`application.Mobile.NetworkJSON()`, not `application.Android`'s, which
exists only under the `android` build tag; and the permission is read at
the moment a download would start, so enabling it takes effect on the
next attempt rather than the next launch.
This commit is contained in:
2026-08-17 10:48:00 -04:00
parent 2c78b58207
commit de2b324e20
12 changed files with 527 additions and 8 deletions
+18
View File
@@ -191,6 +191,24 @@ func NewYellowJacketApp(
yjApp.library.SetJobRegistry(yjApp.jobs)
yjApp.explore.SetJobRegistry(yjApp.jobs)
// Whether this connection is one to spend ~0.6 GB of catalog on
// (plan 016 B4). The probe is injected from here because `explore` is
// imported by `cmd/indexbuild`, which must not link Wails: naming
// `application` there is what `TestIndexToolsDoNotImportWails`
// forbids.
//
// `application.Mobile`, not `application.Android`: the latter exists
// only under the `android` build tag, while `Mobile` is the portable
// name whose desktop implementation is a stub returning "" — which
// parses to "unknown" and refuses nothing. Plan 016 named the tagged
// one; this is the same call by the name every build has.
yjApp.explore.SetNetworkPolicy(
func() explore.Network {
return explore.ParseNetworkJSON(application.Mobile.NetworkJSON())
},
yjApp.appConfig.GetAllowMeteredCatalogDownload,
)
// Let the release prefetch skip albums the user already owns in
// full — those open with no catalog call at all, so warming their
// tracklists spends the most expensive request in the app on