Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,9 @@ sudo systemctl restart led-matrix # after changing config.ini

**Update:** when a newer release is out, the settings page says so and has
an *Update now* button. The Pi looks for one a minute after it starts and
then every six hours; it only asks GitHub, nothing is opened towards the
internet. The button takes about a minute, the panel goes dark briefly, and
then every six hours, and again while the settings page or the app's *More*
is open (at most every ten minutes); it only asks GitHub, nothing is opened
towards the internet. The button takes about a minute, the panel goes dark briefly, and
the page reloads in the new version. The app connected to the display offers
the same under *More*, with a dot on the tab while an update is waiting. The
button looks for the newest release once more before it starts, so it never
Expand Down
10 changes: 7 additions & 3 deletions app/pet.html
Original file line number Diff line number Diff line change
Expand Up @@ -3217,16 +3217,20 @@ <h2 data-t="page.tabs.more"></h2>
// (the Pi then installs the newest release up to the app's version, see startUpdate)
const ahead = u.available && app.aheadOfApp(u.latest);
const upToApp = ahead && app.belowApp(u.current);
// a display older than this app has a release waiting (the app's own), even before the Pi's
// next look has found it: the button makes it look right away
const behind = !u.available && !u.running && app.belowApp(u.current);
const lines = [t("page.app.update_version", { version: u.current })];
if (u.running) lines.push(t("page.app.update_running"));
else if (upToApp) lines.push(t("page.app.update_available", { latest: app.version }));
else if (upToApp || behind) lines.push(t("page.app.update_available", { latest: app.version }));
else if (ahead) lines.push(t("page.app.update_app_first", { latest: u.latest }));
else if (u.available) lines.push(t("page.app.update_available", { latest: u.latest }));
else if (u.failed) lines.push(t("page.app.update_failed"));
$("update-text").textContent = lines.join(" ");
$("update-text").hidden = false;
$("update-start").hidden = (ahead && !upToApp) || (!u.available && !(app.fit() === "display_old" && !u.running));
document.querySelector('[data-view="more"]').classList.toggle("news", u.available && (!ahead || upToApp));
$("update-start").hidden = (ahead && !upToApp)
|| (!u.available && !behind && !(app.fit() === "display_old" && !u.running));
document.querySelector('[data-view="more"]').classList.toggle("news", (u.available && (!ahead || upToApp)) || behind);
}

// The Pi looks for the newest release once more before it starts, so this never waits for its next
Expand Down
16 changes: 16 additions & 0 deletions pi/src/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ export class Updates {
latest: string | null = null;
private running = false;
private failed = false;
/** When the last look started (ms), and the look that is under way. */
private looked = 0;
private looking: Promise<void> | null = null;

/**
* `directory`: where VERSION and REPO are (the app's folder). `beta`: whether betas are wanted
Expand Down Expand Up @@ -97,6 +100,7 @@ export class Updates {
if (!this.enabled) {
return;
}
this.looked = Date.now();
await this.checkUnit();
try {
const tag = this.beta() ? await this.newestBeta() : await this.newestRelease();
Expand All @@ -109,6 +113,18 @@ export class Updates {
}
}

/**
* Looks again in the background when the last look is older than `maxAge` (ms), so somebody
* who opens the app or the settings page sees a release from a minute ago on the next answer,
* not only after the next regular look hours later. Never more than one look at a time.
*/
lookIfOlder(maxAge: number): void {
if (!this.enabled || this.looking || Date.now() - this.looked < maxAge) {
return;
}
this.looking = this.check().finally(() => { this.looking = null; });
}

/** The newest release: .../releases/latest redirects to .../releases/tag/<version> (it skips pre-releases). */
private async newestRelease(): Promise<string> {
const response = await this.request(`https://github.com/${this.repo}/releases/latest`,
Expand Down
4 changes: 4 additions & 0 deletions pi/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const log = getLog("web");
const MAX_BODY = 64 * 1024; // small JSON requests only
const MAX_SAVE = 2 * 1024 * 1024; // a save file loaded on the settings page
const PREVIEW_WAIT = 1; // seconds the preview waits for the next frame at most
const LOOK_AGAIN = 10 * 60_000; // ms after a look for a new release before GET /api/state looks again
// background of the pet app, as "#111318"

// ---------- Pages ----------
Expand Down Expand Up @@ -176,6 +177,9 @@ export function createApp(options: WebOptions): (request: IncomingMessage, respo
return html(page(request, "index.html", { languages: settings.languages() }));

case "GET /api/state":
// the app and the settings page ask for this while they show the update: a release
// published since the last look shows up there within minutes (GitHub allows 60 looks an hour)
updates.lookIfOlder(LOOK_AGAIN);
return json(stateJson());

case "POST /api/mode":
Expand Down
20 changes: 20 additions & 0 deletions pi/test/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,23 @@ test("an app asks for an update up to its own version: the newest release up to
assert.equal(await plain.start("not a version!"), true);
assert.equal(readFileSync(env, "utf-8"), "LED_MATRIX_VERSION=v1.1.0\n");
});

test("a look that is old is done again in the background, one at a time", async () => {
let asked = 0;
const request = (async () => {
asked++;
return { ok: true, status: 200, url: "https://github.com/someone/blobling/releases/tag/v1.1.0" };
}) as unknown as typeof fetch;
const updates = new Updates(release("v1.0.0"), request, systemctl("inactive"));
updates.lookIfOlder(60_000);
updates.lookIfOlder(60_000); // the first one is still under way
await new Promise((resolve) => setTimeout(resolve, 10));
assert.equal(asked, 1);
assert.equal(updates.state().latest, "v1.1.0");
updates.lookIfOlder(60_000); // just looked
await new Promise((resolve) => setTimeout(resolve, 10));
assert.equal(asked, 1);
updates.lookIfOlder(0);
await new Promise((resolve) => setTimeout(resolve, 10));
assert.equal(asked, 2);
});
Loading