feat: a newer app is told in a snackbar on Home and installed from a sheet holding Settings' card, fetched ahead on Wi-Fi (#6452)
release / govulncheck (push) Successful in 21s
release / web (push) Successful in 1m41s
release / go (push) Successful in 1m55s
release / android (push) Successful in 5m19s
release / Build signed APK (releases and dev) (push) Successful in 5m31s
release / integration (push) Successful in 5m39s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 28s
release / Verify release artifacts (tag releases only) (push) Skipped
release / govulncheck (push) Successful in 21s
release / web (push) Successful in 1m41s
release / go (push) Successful in 1m55s
release / android (push) Successful in 5m19s
release / Build signed APK (releases and dev) (push) Successful in 5m31s
release / integration (push) Successful in 5m39s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 28s
release / Verify release artifacts (tag releases only) (push) Skipped
Mirrors Librarian (#6446, #6448), after Gallery's update tray (canon #6367). - Home shows "Version X of the app is on your server." / "…is downloaded and ready." with Update / Install; the button opens a bottom sheet holding the same About card Settings shows, so there is one install path. Each build is told at most once a day, kept on disk. - The shell-wide update banner and its in-memory dismissals are gone. - AppUpdates (singleton) holds the offer and the install, so Home and Settings show the same state. Checks run when either screen opens, on every return to the front, every 5 minutes while shown, and on Home's pull to refresh. - UpdateFetchWorker downloads the newer APK ahead on an unmetered network (battery and storage not low, three tries); the download writes a .part file and keeps it under the build's name only once its size and sha256 match, so Install is usually one tap. - Back from allowing installs, the update the listener asked for carries on (kept in SavedStateHandle) instead of asking them to tap again. - An offer needs a higher code AND a different name: the same app built again at a later minute is not offered. - Server: /api/client/version reports the APK's sha256, worked out from the served file and cached by size and mtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
@@ -128,6 +130,39 @@ func TestClientVersion_200WithBothFiles(t *testing.T) {
|
||||
if resp.SizeBytes != size {
|
||||
t.Errorf("size_bytes: want %d, got %d", size, resp.SizeBytes)
|
||||
}
|
||||
want := sha256.Sum256([]byte("fake apk content"))
|
||||
if resp.SHA256 != hex.EncodeToString(want[:]) {
|
||||
t.Errorf("sha256: want the served file's digest %x, got %q", want, resp.SHA256)
|
||||
}
|
||||
}
|
||||
|
||||
// The digest is cached by size and modification time; a new APK dropped in
|
||||
// place must be answered with its own digest, not the old one's.
|
||||
func TestClientVersion_DigestFollowsAReplacedAPK(t *testing.T) {
|
||||
writeClientAssets(t, `{"name":"a","code":1}`)
|
||||
first := decodeClientVersion(t, getClientVersion(t))
|
||||
dir := os.Getenv("MINSTREL_CLIENT_APK_DIR")
|
||||
apk := filepath.Join(dir, clientAPKFilename)
|
||||
if err := os.WriteFile(apk, []byte("another, longer build"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
second := decodeClientVersion(t, getClientVersion(t))
|
||||
want := sha256.Sum256([]byte("another, longer build"))
|
||||
if second.SHA256 != hex.EncodeToString(want[:]) || second.SHA256 == first.SHA256 {
|
||||
t.Errorf("sha256 after replace: want %x, got %q (before: %q)", want, second.SHA256, first.SHA256)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeClientVersion(t *testing.T, rr *httptest.ResponseRecorder) clientVersionResponse {
|
||||
t.Helper()
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("want 200, got %d (body: %s)", rr.Code, rr.Body.String())
|
||||
}
|
||||
var resp clientVersionResponse
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// A release published before ordering keys were recorded has a name and
|
||||
|
||||
Reference in New Issue
Block a user