mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 22:03:13 -06:00
Both found while watching a real self-update run end to end on the XT245. FileExists now uses roReadFile + type(), which is what BrightSign's own published autozip.brs does (their CheckFile). MatchFiles is for LISTING a directory; as an existence check it has already burned this codebase once, passing a full path as both arguments so it could never return true for anything. Correcting it to a directory plus a bare name did work — I misread a mid-cycle inspection as a second failure and it was not — but roReadFile takes the full path every call site naturally has, needs no reasoning about volume-root semantics, and is the form the vendor ships. The narrower idiom is worth having here precisely because nothing in CI can tell us when this is wrong. CheckPackageUpdate now returns early when a package is already staged. Observed on hardware: the periodic check fired in the gap between staging an archive and the reboot that applies it, and pulled the whole thing down a second time. Harmless on a desk; on a metered or marginal link it is exactly the waste the rest of this release exists to remove. The self-update chain is now proven on hardware, twice: check, download, sha256 and size verify, stage, reboot, staged unpack, move into place without touching screentinker.json, mark done, reboot into it. The player reports 1.9.29-rc5 and its autorun.brs carries the archive's timestamp rather than a hand-copied one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
112 lines
5.1 KiB
Plaintext
112 lines
5.1 KiB
Plaintext
' ScreenTinker — autorun.zip unpacker.
|
|
'
|
|
' Ships INSIDE autorun.zip, at its root. The card (or internal flash) carries a single file —
|
|
' autorun.zip — and this script unpacks it in place, marks it done so it never re-extracts, and
|
|
' reboots into the real host.
|
|
'
|
|
' That is the whole point of the zip: one file to hand someone, or to drop on a hundred cards,
|
|
' instead of four files that must all arrive intact and in the right place. A partially-copied
|
|
' set of loose files boots into something broken; a partially-copied zip simply fails to extract
|
|
' and leaves the player where it was.
|
|
'
|
|
' ⚠️ autorun.brs must NOT sit next to autorun.zip on the storage root — its presence stops the zip
|
|
' being processed at all. autorun.brs belongs INSIDE the zip, which is where the build script puts
|
|
' it (scripts/build-autorun-zip.sh).
|
|
'
|
|
' Unpacks with roBrightPackage, which is what BrightSign's own tooling uses.
|
|
'
|
|
' On compression: roBrightPackage supports deflate32 with default options, PPMd, and "no
|
|
' compression". What it does NOT support is bzip2, LZMA, Deflate64 and Zip64 (the last being what
|
|
' Windows Explorer's built-in zipper produces). We build STORED, which is stricter than required and
|
|
' costs nothing at this size — but note that compression was NOT the cause of the deployment failure
|
|
' this script was blamed for. That was the MatchFiles bug below.
|
|
'
|
|
' Requires BrightSignOS 7.0.60+.
|
|
|
|
' WHERE the archive is. A player may be fed from USB, a card, an SSD, or internal flash — and the
|
|
' unit that drove this port boots from FLASH because its card interface is physically dead. Probing
|
|
' for the file beats assuming a volume: extracting to "SD:/" on a player with no card writes to a
|
|
' volume that does not exist, and the deployment silently does nothing.
|
|
Function SourceRoot() As String
|
|
volumes = ["USB1:", "SD:", "SD2:", "SSD:", "FLASH:"]
|
|
for each v in volumes
|
|
if FileExists(v + "/autorun.zip") then return v
|
|
end for
|
|
return ""
|
|
End Function
|
|
|
|
Sub Main()
|
|
root$ = SourceRoot()
|
|
if root$ = "" then
|
|
print "[st-autozip] no autorun.zip on any volume — nothing to do"
|
|
return
|
|
end if
|
|
zipPath$ = root$ + "/autorun.zip"
|
|
extractPath$ = root$ + "/"
|
|
donePath$ = root$ + "/autorun.zip.done"
|
|
|
|
print "[st-autozip] volume "; root$
|
|
|
|
if not FileExists(extractPath$ + "autorun.zip") then
|
|
print "[st-autozip] no autorun.zip at "; zipPath$; " — nothing to do"
|
|
return
|
|
end if
|
|
|
|
' Idempotence. Without this the player extracts, reboots, extracts again, reboots again —
|
|
' a boot loop that looks like a hardware fault.
|
|
if FileExists(extractPath$ + "autorun.zip.done") then
|
|
print "[st-autozip] already unpacked (autorun.zip.done present) — leaving it alone"
|
|
return
|
|
end if
|
|
|
|
print "[st-autozip] unpacking "; zipPath$
|
|
|
|
package = CreateObject("roBrightPackage", zipPath$)
|
|
if package = invalid then
|
|
print "[st-autozip] ERROR: could not open the archive — is it STORED (no compression)?"
|
|
' Deliberately NOT marking it done: a corrupt, truncated or wrongly-compressed copy should
|
|
' be retried once someone replaces the file, not silently skipped forever.
|
|
return
|
|
end if
|
|
|
|
' Unpack() returns VOID — there is no boolean to test, and `if not package.Unpack(...)` was a
|
|
' type error rather than an error check. Success is proven the only way that actually means
|
|
' anything: the file we came here to install is now on the card.
|
|
package.Unpack(extractPath$)
|
|
|
|
if not FileExists(extractPath$ + "autorun.brs") then
|
|
print "[st-autozip] ERROR: unpack produced no autorun.brs — leaving the archive for a retry"
|
|
return
|
|
end if
|
|
|
|
print "[st-autozip] extracted"
|
|
|
|
' MoveFile/DeleteFile are GLOBAL functions on BrightSign. roFileSystem is a Roku object and does
|
|
' not exist here, so every one of these calls used to return invalid — which meant the archive
|
|
' was never marked done and the player never rebooted into the player it had just installed.
|
|
if not MoveFile(zipPath$, donePath$) then
|
|
print "[st-autozip] ERROR: could not rename the archive; refusing to reboot into a loop"
|
|
return
|
|
end if
|
|
|
|
print "[st-autozip] rebooting into the unpacked player"
|
|
sleep(2000)
|
|
RebootSystem()
|
|
End Sub
|
|
|
|
' Does [path] exist?
|
|
'
|
|
' roReadFile + a type() check — the idiom BrightSign's own boilerplate uses (CheckFile in their
|
|
' published autozip.brs). It takes a FULL PATH, which is what every call site naturally has.
|
|
'
|
|
' MatchFiles is deliberately not used here. It is for LISTING a directory: it takes a directory plus
|
|
' a pattern, returns nothing when the pattern contains a separator, and — as this player
|
|
' demonstrated — does not reliably answer for a volume root like "SSD:/". The first version of this
|
|
' function passed a path as both arguments and could never return true at all; the second passed a
|
|
' directory and a bare name and still answered "no" for a file sitting right there. An existence
|
|
' check that is subtly wrong is worse than none, because every guard built on it silently opens.
|
|
Function FileExists(path As String) As Boolean
|
|
f = CreateObject("roReadFile", path)
|
|
return type(f) = "roReadFile"
|
|
End Function
|