wip(0.S3): the packages migrated by ludic migrate state packages - every package test green

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 14:02:21 +03:00
parent 1e8b5b0523
commit 07505e7ef2
294 changed files with 14996 additions and 14675 deletions

View file

@ -1,49 +1,49 @@
# chain.ludic - the plan made: the files to fetch, in order, and the base the first delta patches
function update__plan_clear() -> void {
update__files = new []string
update__sha = new []string
update__size = new []int
update__delta = new []bool
update__ver = new []string
update__full = new []string
update__total_bytes = 0
update__done_bytes = 0
update__i = 0
function update__plan_clear(update_st: mut UpdateState) -> void {
update_st.update__files = new []string
update_st.update__sha = new []string
update_st.update__size = new []int
update_st.update__delta = new []bool
update_st.update__ver = new []string
update_st.update__full = new []string
update_st.update__total_bytes = 0
update_st.update__done_bytes = 0
update_st.update__i = 0
}
# the plan from a parsed feed; false when there is nothing newer than this version
export function update_plan(feed: Val) -> bool {
update__plan_clear()
let as = update__assets(feed)
export function update_plan(update_st: mut UpdateState, feed: Val) -> bool {
update__plan_clear(update_st)
let as = update__assets(update_st, feed)
let n = update__newest_full(as)
if n < 0 or not update_newer(as[n].ver, update__conf().version) { return false }
if n < 0 or not update_newer(as[n].ver, update__conf(update_st).version) { return false }
let newest = as[n]
update__target = newest.ver
let base = update_packages() + update__sep() + update__full_name(as, update__conf().version)
let chain = update__chain(as, newest.ver)
update_st.update__target = newest.ver
let base = update_packages(update_st) + update__sep() + update__full_name(update_st, as, update__conf(update_st).version)
let chain = update__chain(update_st, as, newest.ver)
var chain_bytes = 0
for i in 0 .. len(chain) { chain_bytes += chain[i].size }
let whole = len(chain) > 0 and len(chain) == update__fulls_after(as)
let whole = len(chain) > 0 and len(chain) == update__fulls_after(update_st, as)
if Fs.exists(base) and whole and chain_bytes < newest.size {
for i in 0 .. len(chain) { update__plan_add(chain[i], update__full_name(as, chain[i].ver)) }
update__old = base
for i in 0 .. len(chain) { update__plan_add(update_st, chain[i], update__full_name(update_st, as, chain[i].ver)) }
update_st.update__old = base
} else {
update__plan_add(newest, newest.file)
update__old = ""
update__plan_add(update_st, newest, newest.file)
update_st.update__old = ""
}
return true
}
# the plan, for a panel or a test
export function update_package_count() -> int {
if update__files == null { return 0 }
return len(update__files)
export function update_package_count(update_st: UpdateState) -> int {
if update_st.update__files == null { return 0 }
return len(update_st.update__files)
}
export function update_plan_file(i: int) -> string { return update__files[i] }
export function update_plan_is_delta(i: int) -> bool { return update__delta[i] }
export function update_plan_version(i: int) -> string { return update__ver[i] }
export function update_target() -> string { return update__target }
export function update_total_bytes() -> int { return update__total_bytes }
export function update_plan_file(update_st: UpdateState, i: int) -> string { return update_st.update__files[i] }
export function update_plan_is_delta(update_st: UpdateState, i: int) -> bool { return update_st.update__delta[i] }
export function update_plan_version(update_st: UpdateState, i: int) -> string { return update_st.update__ver[i] }
export function update_target(update_st: UpdateState) -> string { return update_st.update__target }
export function update_total_bytes(update_st: UpdateState) -> int { return update_st.update__total_bytes }
function update__path(i: int) -> string { return update_packages() + update__sep() + update__files[i] }
function update__path(update_st: mut UpdateState, i: int) -> string { return update_packages(update_st) + update__sep() + update_st.update__files[i] }

View file

@ -44,19 +44,19 @@ function update__up(p: string) -> string {
# the executable's directory
export function update_dir() -> string { return update__up(UpdateWorld.exe()) }
export function update_root() -> string {
let c = update__conf()
export function update_root(update_st: mut UpdateState) -> string {
let c = update__conf(update_st)
if len(c.root) > 0 { return c.root }
if update__mac() { return update__up(update__up(update_dir())) }
return update_dir() + "\\.."
}
# where the packages live: never inside a macOS bundle, which is read-only and which apply replaces
export function update_packages() -> string {
let c = update__conf()
export function update_packages(update_st: mut UpdateState) -> string {
let c = update__conf(update_st)
if len(c.packages) > 0 { return c.packages }
if update__mac() and len(c.mac_packages) > 0 { return c.mac_packages }
return update_root() + update__sep() + "packages"
return update_root(update_st) + update__sep() + "packages"
}
function update__feed_file() -> string {
@ -64,9 +64,9 @@ function update__feed_file() -> string {
return "releases.win.json"
}
function update__updater() -> string {
if update__mac() { return update_root() + "/Contents/MacOS/UpdateMac" }
return update_root() + update__sep() + "Update.exe"
function update__updater(update_st: mut UpdateState) -> string {
if update__mac() { return update_root(update_st) + "/Contents/MacOS/UpdateMac" }
return update_root(update_st) + update__sep() + "Update.exe"
}
# A quarantined Mac app still where it was downloaded runs from a read-only /AppTranslocation/
@ -76,8 +76,8 @@ export function update_translocated() -> bool {
}
# can this copy update itself? A copy Velopack did not install downloads the game again instead.
export function update_installed() -> bool {
if len(update__conf().root) > 0 { return true }
export function update_installed(update_st: mut UpdateState) -> bool {
if len(update__conf(update_st).root) > 0 { return true }
if update_translocated() { return false }
if update__mac() { return Fs.exists(update_dir() + "/UpdateMac") }
return Fs.exists(update_dir() + "/../Update.exe")

View file

@ -1,73 +1,69 @@
# latest.ludic - "a new version is out": latest.json asked once, polled a frame at a time, so a
# slow or absent network costs the frame nothing - it simply never says there is one.
# latest.json is {"version": "0.5.1", "notes": {"en": ["...", ...], "tr": [...]}}
var update__ask: int = 0
var update__ask_h: int = -1
var update__latest: string = ""
var update__notes: Val = null
# once a frame on the screen that shows it; with show_current, the notes of the version being
# played are kept too (a launcher shows them)
export function update_check_tick(show_current: bool) -> void {
if update__ask == UPDATE_ASK_IDLE {
update__ask_h = UpdateNet.get(update__conf().feed + "/" + update__conf().latest)
update__ask = UPDATE_ASKING
if update__ask_h < 0 { update__ask = UPDATE_NONE }
export function update_check_tick(update_st: mut UpdateState, show_current: bool) -> void {
if update_st.update__ask == UPDATE_ASK_IDLE {
update_st.update__ask_h = UpdateNet.get(update__conf(update_st).feed + "/" + update__conf(update_st).latest)
update_st.update__ask = UPDATE_ASKING
if update_st.update__ask_h < 0 { update_st.update__ask = UPDATE_NONE }
return
}
if update__ask != UPDATE_ASKING { return }
let st = UpdateNet.poll(update__ask_h)
if update_st.update__ask != UPDATE_ASKING { return }
let st = UpdateNet.poll(update_st.update__ask_h)
if st < 0 { return }
update__ask = UPDATE_NONE
if st > 0 { update__latest_read(Json.parse(UpdateNet.text(update__ask_h)), show_current) }
UpdateNet.free(update__ask_h)
update__ask_h = -1
update_st.update__ask = UPDATE_NONE
if st > 0 { update__latest_read(update_st, Json.parse(UpdateNet.text(update_st.update__ask_h)), show_current) }
UpdateNet.free(update_st.update__ask_h)
update_st.update__ask_h = -1
}
function update__latest_read(v: Val, show_current: bool) -> void {
function update__latest_read(update_st: mut UpdateState, v: Val, show_current: bool) -> void {
if v == null or Value.kind(v) != 6 or Value.has(v, "version") == 0 { return }
let ver = Value.as_str(Value.get(v, "version"))
let mine = update__conf().version
if update_newer(ver, mine) { update__ask = UPDATE_NEWER }
else if ver == mine and show_current { update__ask = UPDATE_CURRENT }
let mine = update__conf(update_st).version
if update_newer(ver, mine) { update_st.update__ask = UPDATE_NEWER }
else if ver == mine and show_current { update_st.update__ask = UPDATE_CURRENT }
else { return }
update__latest = ver
if Value.has(v, "notes") != 0 { update__notes = Value.get(v, "notes") }
update_st.update__latest = ver
if Value.has(v, "notes") != 0 { update_st.update__notes = Value.get(v, "notes") }
}
export function update_ask_state() -> int { return update__ask }
export function update_latest() -> string { return update__latest }
export function update_shown() -> bool { return update__ask == UPDATE_NEWER or update__ask == UPDATE_CURRENT }
export function update_fresh() -> bool { return update__ask == UPDATE_NEWER }
export function update_ask_state(update_st: UpdateState) -> int { return update_st.update__ask }
export function update_latest(update_st: UpdateState) -> string { return update_st.update__latest }
export function update_shown(update_st: UpdateState) -> bool { return update_st.update__ask == UPDATE_NEWER or update_st.update__ask == UPDATE_CURRENT }
export function update_fresh(update_st: UpdateState) -> bool { return update_st.update__ask == UPDATE_NEWER }
# the notes in this language, else English, else none
export function update_notes(lang: string) -> []string {
export function update_notes(update_st: UpdateState, lang: string) -> []string {
let out = new []string
if update__notes == null or Value.kind(update__notes) != 6 { return out }
if update_st.update__notes == null or Value.kind(update_st.update__notes) != 6 { return out }
var key = lang
if Value.has(update__notes, key) == 0 { key = "en" }
if Value.has(update__notes, key) == 0 { return out }
let l = Value.get(update__notes, key)
if Value.has(update_st.update__notes, key) == 0 { key = "en" }
if Value.has(update_st.update__notes, key) == 0 { return out }
let l = Value.get(update_st.update__notes, key)
for i in 0 .. Value.count(l) { push(out, Value.as_str(Value.at(l, i))) }
return out
}
# back to before anything was asked or installed (tests)
export function update_reset() -> void {
if update__ask_h >= 0 { UpdateNet.free(update__ask_h) }
update__ask_h = -1
update__ask = UPDATE_ASK_IDLE
update__latest = ""
update__notes = null
update__release()
update__state = UPDATE_IDLE
update__err = UPDATE_ERR_NONE
update__target = ""
update__files = null
update__total_bytes = 0
update__done_bytes = 0
update__i = 0
update__rate = 0
update__rate_t = -1
q_clear(update_facts())
export function update_reset(base_st: mut BaseState, update_st: mut UpdateState) -> void {
if update_st.update__ask_h >= 0 { UpdateNet.free(update_st.update__ask_h) }
update_st.update__ask_h = -1
update_st.update__ask = UPDATE_ASK_IDLE
update_st.update__latest = ""
update_st.update__notes = null
update__release(update_st)
update_st.update__state = UPDATE_IDLE
update_st.update__err = UPDATE_ERR_NONE
update_st.update__target = ""
update_st.update__files = null
update_st.update__total_bytes = 0
update_st.update__done_bytes = 0
update_st.update__i = 0
update_st.update__rate = 0
update_st.update__rate_t = -1
q_clear(base_st, update_facts(base_st, update_st))
}

View file

@ -10,13 +10,13 @@ property UpdateAsset {
}
# this app's packages in the feed ({"Assets": [{PackageId, Version, Type, FileName, SHA256, Size}]})
function update__assets(feed: Val) -> []UpdateAsset {
function update__assets(update_st: mut UpdateState, feed: Val) -> []UpdateAsset {
let out = new []UpdateAsset
if feed == null or Value.kind(feed) != 6 or Value.has(feed, "Assets") == 0 { return out }
let all = Value.get(feed, "Assets")
for i in 0 .. Value.count(all) {
let a = Value.at(all, i)
if sv_str(a, "PackageId", "") != update__conf().app_id { continue }
if sv_str(a, "PackageId", "") != update__conf(update_st).app_id { continue }
let x = new UpdateAsset
x.ver = sv_str(a, "Version", "")
x.file = sv_str(a, "FileName", "")
@ -31,9 +31,9 @@ function update__assets(feed: Val) -> []UpdateAsset {
# What the feed calls the FULL package of version v. Never built here: Velopack puts the channel in
# a macOS name (App-0.3.4-osx-full.nupkg) and not a Windows one, and it is both the name a patch
# writes and the name the next update looks for as its base.
function update__full_name(as: []UpdateAsset, v: string) -> string {
function update__full_name(update_st: mut UpdateState, as: []UpdateAsset, v: string) -> string {
for i in 0 .. len(as) { if not as[i].delta and as[i].ver == v { return as[i].file } }
return update__conf().app_id + "-" + v + "-full.nupkg"
return update__conf(update_st).app_id + "-" + v + "-full.nupkg"
}
function update__newest_full(as: []UpdateAsset) -> int {
@ -46,9 +46,9 @@ function update__newest_full(as: []UpdateAsset) -> int {
}
# the deltas above this version up to the newest, oldest first
function update__chain(as: []UpdateAsset, newest: string) -> []UpdateAsset {
function update__chain(update_st: mut UpdateState, as: []UpdateAsset, newest: string) -> []UpdateAsset {
let out = new []UpdateAsset
let mine = update__conf().version
let mine = update__conf(update_st).version
for i in 0 .. len(as) {
let a = as[i]
if not a.delta or not update_newer(a.ver, mine) or update_newer(a.ver, newest) { continue }
@ -65,18 +65,18 @@ function update__chain(as: []UpdateAsset, newest: string) -> []UpdateAsset {
return out
}
function update__fulls_after(as: []UpdateAsset) -> int {
function update__fulls_after(update_st: mut UpdateState, as: []UpdateAsset) -> int {
var n = 0
for i in 0 .. len(as) { if not as[i].delta and update_newer(as[i].ver, update__conf().version) { n += 1 } }
for i in 0 .. len(as) { if not as[i].delta and update_newer(as[i].ver, update__conf(update_st).version) { n += 1 } }
return n
}
function update__plan_add(a: UpdateAsset, full: string) -> void {
push(update__files, a.file)
push(update__sha, a.sha)
push(update__size, a.size)
push(update__delta, a.delta)
push(update__ver, a.ver)
push(update__full, full)
update__total_bytes += a.size
function update__plan_add(update_st: mut UpdateState, a: UpdateAsset, full: string) -> void {
push(update_st.update__files, a.file)
push(update_st.update__sha, a.sha)
push(update_st.update__size, a.size)
push(update_st.update__delta, a.delta)
push(update_st.update__ver, a.ver)
push(update_st.update__full, full)
update_st.update__total_bytes += a.size
}

View file

@ -1,70 +1,70 @@
# queries.ludic - what a panel draws: the step, the bar, the pace, and why it stopped
export function update_state() -> int { return update__state }
export function update_error() -> int { return update__err }
export function update_busy() -> bool { return update__state != UPDATE_IDLE and update__state != UPDATE_FAILED }
export function update_failed() -> bool { return update__state == UPDATE_FAILED }
export function update_state(update_st: UpdateState) -> int { return update_st.update__state }
export function update_error(update_st: UpdateState) -> int { return update_st.update__err }
export function update_busy(update_st: UpdateState) -> bool { return update_st.update__state != UPDATE_IDLE and update_st.update__state != UPDATE_FAILED }
export function update_failed(update_st: UpdateState) -> bool { return update_st.update__state == UPDATE_FAILED }
# checking and patching have no fraction to give: a panel's bar sweeps instead of filling
export function update_working() -> bool { return update__state == UPDATE_CHECK or update__state == UPDATE_PATCH }
export function update_working(update_st: UpdateState) -> bool { return update_st.update__state == UPDATE_CHECK or update_st.update__state == UPDATE_PATCH }
# the package being fetched, checked or patched (0-based), its name, size and version
export function update_index() -> int { return update__i }
export function update_index(update_st: UpdateState) -> int { return update_st.update__i }
export function update_file() -> string {
if update__files == null or update__i >= len(update__files) { return "" }
return update__files[update__i]
export function update_file(update_st: UpdateState) -> string {
if update_st.update__files == null or update_st.update__i >= len(update_st.update__files) { return "" }
return update_st.update__files[update_st.update__i]
}
export function update_file_size() -> int {
if update__size == null or update__i >= len(update__size) { return 0 }
return update__size[update__i]
export function update_file_size(update_st: UpdateState) -> int {
if update_st.update__size == null or update_st.update__i >= len(update_st.update__size) { return 0 }
return update_st.update__size[update_st.update__i]
}
export function update_version_at() -> string {
if update__ver == null or update__i >= len(update__ver) { return "" }
return update__ver[update__i]
export function update_version_at(update_st: UpdateState) -> string {
if update_st.update__ver == null or update_st.update__i >= len(update_st.update__ver) { return "" }
return update_st.update__ver[update_st.update__i]
}
# bytes of the package in flight, and of the whole plan
export function update_file_received() -> int {
if update__state != UPDATE_FETCH or update__h < 0 { return 0 }
return UpdateNet.received(update__h)
export function update_file_received(update_st: UpdateState) -> int {
if update_st.update__state != UPDATE_FETCH or update_st.update__h < 0 { return 0 }
return UpdateNet.received(update_st.update__h)
}
export function update_received() -> int { return update__done_bytes + update_file_received() }
export function update_received(update_st: UpdateState) -> int { return update_st.update__done_bytes + update_file_received(update_st) }
# how far, 0..1000, over every byte of the plan (the file being checked counts as fetched)
export function update_progress() -> int {
if update__total_bytes <= 0 { return 0 }
var got = update__done_bytes
if update__state == UPDATE_FETCH { got = update_received() }
if update__state == UPDATE_CHECK { got = update__done_bytes + update__size[update__i] }
return min(1000, got / max(1, update__total_bytes / 1000))
export function update_progress(update_st: UpdateState) -> int {
if update_st.update__total_bytes <= 0 { return 0 }
var got = update_st.update__done_bytes
if update_st.update__state == UPDATE_FETCH { got = update_received(update_st) }
if update_st.update__state == UPDATE_CHECK { got = update_st.update__done_bytes + update_st.update__size[update_st.update__i] }
return min(1000, got / max(1, update_st.update__total_bytes / 1000))
}
# seconds a check or a patch has been running
export function update_step_seconds() -> int { return (UpdateWorld.now_ms() - update__step_t0) / 1000 }
export function update_step_seconds(update_st: UpdateState) -> int { return (UpdateWorld.now_ms() - update_st.update__step_t0) / 1000 }
# seconds left at the eased pace; -1 until the pace is known
export function update_eta_seconds() -> int {
if update__state != UPDATE_FETCH or update__rate <= 0 { return -1 }
return (update__total_bytes - update_received()) / update__rate + 1
export function update_eta_seconds(update_st: UpdateState) -> int {
if update_st.update__state != UPDATE_FETCH or update_st.update__rate <= 0 { return -1 }
return (update_st.update__total_bytes - update_received(update_st)) / update_st.update__rate + 1
}
# every half second: the pace, eased so the estimate does not jump
function update__rate_tick() -> void {
if update__state != UPDATE_FETCH { return }
function update__rate_tick(update_st: mut UpdateState) -> void {
if update_st.update__state != UPDATE_FETCH { return }
let now = UpdateWorld.now_ms()
let got = update_received()
if update__rate_t < 0 {
update__rate_t = now
update__rate_bytes = got
let got = update_received(update_st)
if update_st.update__rate_t < 0 {
update_st.update__rate_t = now
update_st.update__rate_bytes = got
return
}
let ms = now - update__rate_t
let ms = now - update_st.update__rate_t
if ms < 500 { return }
let per_s = (got - update__rate_bytes) / ms * 1000
if update__rate == 0 { update__rate = per_s } else { update__rate = (update__rate * 3 + per_s) / 4 }
update__rate_t = now
update__rate_bytes = got
let per_s = (got - update_st.update__rate_bytes) / ms * 1000
if update_st.update__rate == 0 { update_st.update__rate = per_s } else { update_st.update__rate = (update_st.update__rate * 3 + per_s) / 4 }
update_st.update__rate_t = now
update_st.update__rate_bytes = got
}

View file

@ -51,47 +51,53 @@ export property UpdateFact {
bytes: int = 0
}
var update__cfg: UpdateConfig = null
var update__state: int = 0
var update__err: int = 0
var update__h: int = -1 # the request
var update__proc: int = -1 # the child process
var update__target: string = ""
var update__files: []string = null # the plan: file names, hashes, sizes, delta or not, versions
var update__sha: []string = null
var update__size: []int = null
var update__delta: []bool = null
var update__ver: []string = null
var update__full: []string = null # what the feed calls the FULL package of that version
var update__i: int = 0
var update__done_bytes: int = 0
var update__total_bytes: int = 0
var update__old: string = "" # the full package the next delta patches
var update__package: string = "" # what apply installs
var update__step_t0: int = 0 # when a check or a patch began, ms
var update__rate: int = 0 # bytes a second, eased
var update__rate_t: int = -1
var update__rate_bytes: int = 0
var update__facts: Queue<UpdateFact> = null
export function update_config(c: UpdateConfig) -> void { update__cfg = c }
function update__conf() -> UpdateConfig {
if update__cfg == null { update__cfg = new UpdateConfig }
return update__cfg
export state UpdateState {
update__ask: int = 0
update__ask_h: int = -1
update__latest: string = ""
update__notes: Val = null
update__cfg: UpdateConfig = null
update__state: int = 0
update__err: int = 0
update__h: int = -1 # the request
update__proc: int = -1 # the child process
update__target: string = ""
update__files: []string = null # the plan: file names, hashes, sizes, delta or not, versions
update__sha: []string = null
update__size: []int = null
update__delta: []bool = null
update__ver: []string = null
update__full: []string = null # what the feed calls the FULL package of that version
update__i: int = 0
update__done_bytes: int = 0
update__total_bytes: int = 0
update__old: string = "" # the full package the next delta patches
update__package: string = "" # what apply installs
update__step_t0: int = 0 # when a check or a patch began, ms
update__rate: int = 0 # bytes a second, eased
update__rate_t: int = -1
update__rate_bytes: int = 0
update__facts: Queue<UpdateFact> = null
}
export function update_facts() -> Queue<UpdateFact> {
if update__facts == null { update__facts = queue_new("update.facts") }
return update__facts
export function update_config(update_st: mut UpdateState, c: UpdateConfig) -> void { update_st.update__cfg = c }
function update__conf(update_st: mut UpdateState) -> UpdateConfig {
if update_st.update__cfg == null { update_st.update__cfg = new UpdateConfig }
return update_st.update__cfg
}
function update__fact(what: int) -> void {
export function update_facts(base_st: mut BaseState, update_st: mut UpdateState) -> Queue<UpdateFact> {
if update_st.update__facts == null { update_st.update__facts = queue_new(base_st, "update.facts") }
return update_st.update__facts
}
function update__fact(base_st: mut BaseState, update_st: mut UpdateState, what: int) -> void {
let f = new UpdateFact
f.what = what
f.error = update__err
f.target = update__target
f.packages = update_package_count()
f.bytes = update__total_bytes
q_push(update_facts(), f)
f.error = update_st.update__err
f.target = update_st.update__target
f.packages = update_package_count(update_st)
f.bytes = update_st.update__total_bytes
q_push(base_st, update_facts(base_st, update_st), f)
}

View file

@ -1,87 +1,87 @@
# steps.ludic - each step started: the feed asked, a package streamed into the packages folder, its
# SHA-256 by a child (Get-FileHash, shasum) so the frame keeps drawing, a patch, the apply
# the Update button: false (and nothing started) when Velopack did not install this copy
export function update_start() -> bool {
if not update_installed() {
update__err = UPDATE_ERR_NOT_INSTALLED
export function update_start(base_st: mut BaseState, update_st: mut UpdateState) -> bool {
if not update_installed(update_st) {
update_st.update__err = UPDATE_ERR_NOT_INSTALLED
return false
}
update__release()
Fs.mkdir(update_packages())
update__err = UPDATE_ERR_NONE
update__h = UpdateNet.get(update__conf().feed + "/" + update__feed_file())
if update__h < 0 {
update__fail(UPDATE_ERR_UNREACHABLE)
update__release(update_st)
Fs.mkdir(update_packages(update_st))
update_st.update__err = UPDATE_ERR_NONE
update_st.update__h = UpdateNet.get(update__conf(update_st).feed + "/" + update__feed_file())
if update_st.update__h < 0 {
update__fail(base_st, update_st, UPDATE_ERR_UNREACHABLE)
return true
}
update__state = UPDATE_FEED
update_st.update__state = UPDATE_FEED
return true
}
function update__release() -> void {
if update__h >= 0 { UpdateNet.free(update__h) }
update__h = -1
if update__proc >= 0 { UpdateProc.free(update__proc) }
update__proc = -1
function update__release(update_st: mut UpdateState) -> void {
if update_st.update__h >= 0 { UpdateNet.free(update_st.update__h) }
update_st.update__h = -1
if update_st.update__proc >= 0 { UpdateProc.free(update_st.update__proc) }
update_st.update__proc = -1
}
function update__fail(err: int) -> void {
update__release()
update__err = err
update__state = UPDATE_FAILED
update__fact(UPDATE_STOPPED)
function update__fail(base_st: mut BaseState, update_st: mut UpdateState, err: int) -> void {
update__release(update_st)
update_st.update__err = err
update_st.update__state = UPDATE_FAILED
update__fact(base_st, update_st, UPDATE_STOPPED)
}
# fetch file i, unless a copy of the right size is there already - then straight to its check
function update__fetch(i: int) -> void {
update__i = i
let p = update__path(i)
if Fs.exists(p) and Fs.size(p) == update__size[i] {
update__check(i)
function update__fetch(base_st: mut BaseState, update_st: mut UpdateState, i: int) -> void {
update_st.update__i = i
let p = update__path(update_st, i)
if Fs.exists(p) and Fs.size(p) == update_st.update__size[i] {
update__check(base_st, update_st, i)
return
}
if Fs.exists(p) { Fs.remove(p) }
update__h = UpdateNet.download(update__conf().feed + "/" + update__files[i], p)
if update__h < 0 {
update__fail(UPDATE_ERR_UNREACHABLE)
update_st.update__h = UpdateNet.download(update__conf(update_st).feed + "/" + update_st.update__files[i], p)
if update_st.update__h < 0 {
update__fail(base_st, update_st, UPDATE_ERR_UNREACHABLE)
return
}
update__rate_t = -1
update__state = UPDATE_FETCH
update_st.update__rate_t = -1
update_st.update__state = UPDATE_FETCH
}
function update__hash_file() -> string {
if len(update__conf().scratch) > 0 { return update__conf().scratch }
function update__hash_file(update_st: mut UpdateState) -> string {
if len(update__conf(update_st).scratch) > 0 { return update__conf(update_st).scratch }
return Os.temp_dir() + "/ludic-update-hash.txt"
}
# no cmd.exe: it parses its own command line and would not survive Process.spawn's quoting
function update__check(i: int) -> void {
let out = update__hash_file()
function update__check(base_st: mut BaseState, update_st: mut UpdateState, i: int) -> void {
let out = update__hash_file(update_st)
if Fs.exists(out) { Fs.remove(out) }
let args = new []string
if UpdateWorld.platform() == "windows" {
push(args, "-NoProfile")
push(args, "-NonInteractive")
push(args, "-Command")
push(args, "(Get-FileHash -Algorithm SHA256 -LiteralPath " + update__psq(update__path(i)) + ").Hash | Out-File -Encoding ascii -LiteralPath " + update__psq(out))
update__proc = UpdateProc.spawn(update__powershell(), args)
push(args, "(Get-FileHash -Algorithm SHA256 -LiteralPath " + update__psq(update__path(update_st, i)) + ").Hash | Out-File -Encoding ascii -LiteralPath " + update__psq(out))
update_st.update__proc = UpdateProc.spawn(update__powershell(), args)
} else {
push(args, "-c")
push(args, "shasum -a 256 '" + update__path(i) + "' | cut -d' ' -f1 > '" + out + "'")
update__proc = UpdateProc.spawn("/bin/sh", args)
push(args, "shasum -a 256 '" + update__path(update_st, i) + "' | cut -d' ' -f1 > '" + out + "'")
update_st.update__proc = UpdateProc.spawn("/bin/sh", args)
}
if update__proc < 0 {
update__fail(UPDATE_ERR_CHECK)
if update_st.update__proc < 0 {
update__fail(base_st, update_st, UPDATE_ERR_CHECK)
return
}
update__state = UPDATE_CHECK
update__step_t0 = UpdateWorld.now_ms()
update_st.update__state = UPDATE_CHECK
update_st.update__step_t0 = UpdateWorld.now_ms()
}
# the hash the child wrote: the first line with anything on it
function update__hash_read() -> string {
let s = Fs.read_text(update__hash_file())
function update__hash_read(update_st: mut UpdateState) -> string {
let s = Fs.read_text(update__hash_file(update_st))
if s == null { return "" }
let lines = Text.split(s, "\n")
for i in 0 .. len(lines) {

View file

@ -4,40 +4,42 @@ import "ludic.update"
import "ludic.base"
program UpdateTest {
numbers float
var net_answer: int = -1
var net_text: string = ""
var net_urls: []string = null
var net_paths: []string = null
var net_got: int = 0
var proc_code: int = -1
var proc_paths: []string = null
var proc_args: []string = null
var exe: string = ""
state UpdatetestState {
net_answer: int = -1
net_text: string = ""
net_urls: []string = null
net_paths: []string = null
net_got: int = 0
proc_code: int = -1
proc_paths: []string = null
proc_args: []string = null
exe: string = ""
}
function fake_platform() -> string { return "macos" }
function fake_exe() -> string { return exe }
function fake_exe(updatetest_st: UpdatetestState) -> string { return updatetest_st.exe }
function fake_pid() -> int { return 4242 }
function fake_ms() -> int { return 0 }
function fake_get(url: string) -> int {
push(net_urls, url)
push(net_paths, "")
return len(net_urls) - 1
function fake_get(updatetest_st: mut UpdatetestState, url: string) -> int {
push(updatetest_st.net_urls, url)
push(updatetest_st.net_paths, "")
return len(updatetest_st.net_urls) - 1
}
function fake_download(url: string, path: string) -> int {
push(net_urls, url)
push(net_paths, path)
return len(net_urls) - 1
function fake_download(updatetest_st: mut UpdatetestState, url: string, path: string) -> int {
push(updatetest_st.net_urls, url)
push(updatetest_st.net_paths, path)
return len(updatetest_st.net_urls) - 1
}
function fake_poll(h: int) -> int { return net_answer }
function fake_text(h: int) -> string { return net_text }
function fake_received(h: int) -> int { return net_got }
function fake_poll(updatetest_st: UpdatetestState, h: int) -> int { return updatetest_st.net_answer }
function fake_text(updatetest_st: UpdatetestState, h: int) -> string { return updatetest_st.net_text }
function fake_received(updatetest_st: UpdatetestState, h: int) -> int { return updatetest_st.net_got }
function fake_free(h: int) -> void { }
function fake_spawn(path: string, args: []string) -> int {
push(proc_paths, path)
push(proc_args, Text.join(args, " "))
return len(proc_paths) - 1
function fake_spawn(updatetest_st: mut UpdatetestState, path: string, args: []string) -> int {
push(updatetest_st.proc_paths, path)
push(updatetest_st.proc_args, Text.join(args, " "))
return len(updatetest_st.proc_paths) - 1
}
function fake_proc_poll(h: int) -> int { return proc_code }
function fake_proc_poll(updatetest_st: UpdatetestState, h: int) -> int { return updatetest_st.proc_code }
function fake_proc_free(h: int) -> void { }
bind UpdateWorld { platform: fn fake_platform, exe: fn fake_exe, pid: fn fake_pid, now_ms: fn fake_ms }
@ -48,16 +50,16 @@ program UpdateTest {
function pkgs() -> string { return dir() + "/packages" }
function hashf() -> string { return dir() + "/hash.txt" }
function fresh(root: bool) -> void {
net_answer = -1
net_text = ""
net_urls = new []string
net_paths = new []string
net_got = 0
proc_code = -1
proc_paths = new []string
proc_args = new []string
exe = dir() + "/Game.app/Contents/MacOS/Game"
function fresh(base_st: mut BaseState, update_st: mut UpdateState, updatetest_st: mut UpdatetestState, root: bool) -> void {
updatetest_st.net_answer = -1
updatetest_st.net_text = ""
updatetest_st.net_urls = new []string
updatetest_st.net_paths = new []string
updatetest_st.net_got = 0
updatetest_st.proc_code = -1
updatetest_st.proc_paths = new []string
updatetest_st.proc_args = new []string
updatetest_st.exe = dir() + "/Game.app/Contents/MacOS/Game"
System.run("rm -rf '" + dir() + "'")
Fs.mkdir(dir())
Fs.mkdir(pkgs())
@ -68,8 +70,8 @@ program UpdateTest {
if root { c.root = dir() + "/Game.app" }
c.packages = pkgs()
c.scratch = hashf()
update_config(c)
update_reset()
update_config(update_st, c)
update_reset(base_st, update_st)
}
function asset(ty: string, ver: string, size: int) -> string {
@ -85,18 +87,18 @@ program UpdateTest {
}
function seed_base() -> void { Fs.write_text(pkgs() + "/Game-1.0.0-osx-full.nupkg", "base") }
function plan_of(text: string) -> bool { return update_plan(Json.parse(text)) }
function plan_of(update_st: mut UpdateState, text: string) -> bool { return update_plan(update_st, Json.parse(text)) }
function facts_of(what: int) -> int {
let fs = q_drain(update_facts())
function facts_of(base_st: mut BaseState, update_st: mut UpdateState, what: int) -> int {
let fs = q_drain(base_st, update_facts(base_st, update_st))
var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n
}
function hash_is(h: string) -> void { Fs.write_text(hashf(), h + "\n") }
function last_args() -> string { return proc_args[len(proc_args) - 1] }
function last_url() -> string { return net_urls[len(net_urls) - 1] }
function last_args(updatetest_st: UpdatetestState) -> string { return updatetest_st.proc_args[len(updatetest_st.proc_args) - 1] }
function last_url(updatetest_st: UpdatetestState) -> string { return updatetest_st.net_urls[len(updatetest_st.net_urls) - 1] }
test "versions compare a number at a time" {
expect(update_newer("0.10.2", "0.9.9"))
@ -105,166 +107,166 @@ program UpdateTest {
expect(not update_newer("0.4.9", "0.5.0"))
}
test "a newer version is shown with its notes, in the language asked for or English" {
fresh(true)
update_check_tick(false)
expect(last_url() == "http://feed/latest.json")
net_text = "{\"version\":\"1.2.0\",\"notes\":{\"en\":[\"Fish.\",\"Boats.\"],\"tr\":[\"Balik.\"]}}"
net_answer = 1
update_check_tick(false)
expect(update_fresh())
expect(update_latest() == "1.2.0")
expect_eq(len(update_notes("tr")), 1)
expect_eq(len(update_notes("de")), 2)
test "a newer version is shown with its notes, in the language asked for or English" (base_st: mut BaseState, update_st: mut UpdateState, updatetest_st: mut UpdatetestState) {
fresh(base_st, update_st, updatetest_st, true)
update_check_tick(update_st, false)
expect(last_url(updatetest_st) == "http://feed/latest.json")
updatetest_st.net_text = "{\"version\":\"1.2.0\",\"notes\":{\"en\":[\"Fish.\",\"Boats.\"],\"tr\":[\"Balik.\"]}}"
updatetest_st.net_answer = 1
update_check_tick(update_st, false)
expect(update_fresh(update_st))
expect(update_latest(update_st) == "1.2.0")
expect_eq(len(update_notes(update_st, "tr")), 1)
expect_eq(len(update_notes(update_st, "de")), 2)
}
test "the version being played is shown only when asked" {
fresh(true)
net_text = "{\"version\":\"1.0.0\",\"notes\":{\"en\":[\"x\"]}}"
net_answer = 1
update_check_tick(false)
update_check_tick(false)
expect(not update_shown())
update_reset()
update_check_tick(true)
update_check_tick(true)
expect_eq(update_ask_state(), UPDATE_CURRENT)
update_reset()
net_answer = 0
update_check_tick(true)
update_check_tick(true)
expect_eq(update_ask_state(), UPDATE_NONE)
test "the version being played is shown only when asked" (base_st: mut BaseState, update_st: mut UpdateState, updatetest_st: mut UpdatetestState) {
fresh(base_st, update_st, updatetest_st, true)
updatetest_st.net_text = "{\"version\":\"1.0.0\",\"notes\":{\"en\":[\"x\"]}}"
updatetest_st.net_answer = 1
update_check_tick(update_st, false)
update_check_tick(update_st, false)
expect(not update_shown(update_st))
update_reset(base_st, update_st)
update_check_tick(update_st, true)
update_check_tick(update_st, true)
expect_eq(update_ask_state(update_st), UPDATE_CURRENT)
update_reset(base_st, update_st)
updatetest_st.net_answer = 0
update_check_tick(update_st, true)
update_check_tick(update_st, true)
expect_eq(update_ask_state(update_st), UPDATE_NONE)
}
test "with the base on disk and every delta there, the plan is the chain, oldest first" {
fresh(true)
test "with the base on disk and every delta there, the plan is the chain, oldest first" (base_st: mut BaseState, update_st: mut UpdateState, updatetest_st: mut UpdatetestState) {
fresh(base_st, update_st, updatetest_st, true)
seed_base()
expect(plan_of(feed(true)))
expect_eq(update_package_count(), 2)
expect(update_plan_is_delta(0))
expect(update_plan_version(0) == "1.1.0")
expect(update_plan_version(1) == "1.2.0")
expect(update_target() == "1.2.0")
expect(plan_of(update_st, feed(true)))
expect_eq(update_package_count(update_st), 2)
expect(update_plan_is_delta(update_st, 0))
expect(update_plan_version(update_st, 0) == "1.1.0")
expect(update_plan_version(update_st, 1) == "1.2.0")
expect(update_target(update_st) == "1.2.0")
}
test "no base, or a missing delta, is the whole newest package" {
fresh(true)
expect(plan_of(feed(true)))
expect_eq(update_package_count(), 1)
expect(not update_plan_is_delta(0))
expect(update_plan_file(0) == "Game-1.2.0-osx-full.nupkg")
test "no base, or a missing delta, is the whole newest package" (base_st: mut BaseState, update_st: mut UpdateState, updatetest_st: mut UpdatetestState) {
fresh(base_st, update_st, updatetest_st, true)
expect(plan_of(update_st, feed(true)))
expect_eq(update_package_count(update_st), 1)
expect(not update_plan_is_delta(update_st, 0))
expect(update_plan_file(update_st, 0) == "Game-1.2.0-osx-full.nupkg")
seed_base()
expect(plan_of(feed(false)))
expect_eq(update_package_count(), 1)
expect(plan_of(update_st, feed(false)))
expect_eq(update_package_count(update_st), 1)
}
test "nothing newer is no plan" {
fresh(true)
expect(not plan_of("{\"Assets\":[" + asset("Full", "1.0.0", 900) + "]}"))
expect(not plan_of("{}"))
test "nothing newer is no plan" (base_st: mut BaseState, update_st: mut UpdateState, updatetest_st: mut UpdatetestState) {
fresh(base_st, update_st, updatetest_st, true)
expect(not plan_of(update_st, "{\"Assets\":[" + asset("Full", "1.0.0", 900) + "]}"))
expect(not plan_of(update_st, "{}"))
}
test "a copy Velopack did not install does not start" {
fresh(false)
expect(not update_installed())
expect(not update_start())
expect_eq(update_error(), UPDATE_ERR_NOT_INSTALLED)
test "a copy Velopack did not install does not start" (base_st: mut BaseState, update_st: mut UpdateState, updatetest_st: mut UpdatetestState) {
fresh(base_st, update_st, updatetest_st, false)
expect(not update_installed(update_st))
expect(not update_start(base_st, update_st))
expect_eq(update_error(update_st), UPDATE_ERR_NOT_INSTALLED)
Fs.mkdir(dir() + "/Game.app")
Fs.mkdir(dir() + "/Game.app/Contents")
Fs.mkdir(dir() + "/Game.app/Contents/MacOS")
Fs.write_text(dir() + "/Game.app/Contents/MacOS/UpdateMac", "x")
expect(update_installed())
exe = "/private/var/folders/x/AppTranslocation/y/Game.app/Contents/MacOS/Game"
expect(not update_installed())
expect(update_installed(update_st))
updatetest_st.exe = "/private/var/folders/x/AppTranslocation/y/Game.app/Contents/MacOS/Game"
expect(not update_installed(update_st))
}
test "the whole package: fetched, checked, applied, and the game told to go" {
fresh(true)
expect(update_start())
expect(last_url() == "http://feed/releases.osx.json")
net_text = feed(true)
net_answer = 1
update_tick()
expect_eq(update_state(), UPDATE_FETCH)
expect_eq(facts_of(UPDATE_STARTED), 1)
expect(last_url() == "http://feed/Game-1.2.0-osx-full.nupkg")
net_got = 500
expect(update_progress() >= 490 and update_progress() <= 510)
update_tick()
expect_eq(update_state(), UPDATE_CHECK)
expect(Text.contains(last_args(), "shasum -a 256"))
test "the whole package: fetched, checked, applied, and the game told to go" (base_st: mut BaseState, update_st: mut UpdateState, updatetest_st: mut UpdatetestState) {
fresh(base_st, update_st, updatetest_st, true)
expect(update_start(base_st, update_st))
expect(last_url(updatetest_st) == "http://feed/releases.osx.json")
updatetest_st.net_text = feed(true)
updatetest_st.net_answer = 1
update_tick(base_st, update_st)
expect_eq(update_state(update_st), UPDATE_FETCH)
expect_eq(facts_of(base_st, update_st, UPDATE_STARTED), 1)
expect(last_url(updatetest_st) == "http://feed/Game-1.2.0-osx-full.nupkg")
updatetest_st.net_got = 500
expect(update_progress(update_st) >= 490 and update_progress(update_st) <= 510)
update_tick(base_st, update_st)
expect_eq(update_state(update_st), UPDATE_CHECK)
expect(Text.contains(last_args(updatetest_st), "shasum -a 256"))
hash_is("AB1.2.0")
proc_code = 0
update_tick()
expect_eq(update_state(), UPDATE_APPLY)
expect(Text.contains(last_args(), "apply --silent --waitPid 4242 --package"))
expect(Text.contains(last_args(), "--rootDir"))
expect(Text.ends_with(proc_paths[len(proc_paths) - 1], "Game.app/Contents/MacOS/UpdateMac"))
expect_eq(facts_of(UPDATE_APPLYING), 1)
updatetest_st.proc_code = 0
update_tick(base_st, update_st)
expect_eq(update_state(update_st), UPDATE_APPLY)
expect(Text.contains(last_args(updatetest_st), "apply --silent --waitPid 4242 --package"))
expect(Text.contains(last_args(updatetest_st), "--rootDir"))
expect(Text.ends_with(updatetest_st.proc_paths[len(updatetest_st.proc_paths) - 1], "Game.app/Contents/MacOS/UpdateMac"))
expect_eq(facts_of(base_st, update_st, UPDATE_APPLYING), 1)
}
test "a wrong hash stops it and deletes the file" {
fresh(true)
update_start()
net_text = feed(true)
net_answer = 1
update_tick()
test "a wrong hash stops it and deletes the file" (base_st: mut BaseState, update_st: mut UpdateState, updatetest_st: mut UpdatetestState) {
fresh(base_st, update_st, updatetest_st, true)
update_start(base_st, update_st)
updatetest_st.net_text = feed(true)
updatetest_st.net_answer = 1
update_tick(base_st, update_st)
Fs.write_text(pkgs() + "/Game-1.2.0-osx-full.nupkg", "junk")
update_tick()
update_tick(base_st, update_st)
hash_is("FFFF")
proc_code = 0
update_tick()
expect(update_failed())
expect_eq(update_error(), UPDATE_ERR_CORRUPT)
updatetest_st.proc_code = 0
update_tick(base_st, update_st)
expect(update_failed(update_st))
expect_eq(update_error(update_st), UPDATE_ERR_CORRUPT)
expect(not Fs.exists(pkgs() + "/Game-1.2.0-osx-full.nupkg"))
expect_eq(facts_of(UPDATE_STOPPED), 1)
expect_eq(facts_of(base_st, update_st, UPDATE_STOPPED), 1)
}
test "a failed download stops it" {
fresh(true)
update_start()
net_text = feed(true)
net_answer = 1
update_tick()
net_answer = 0
update_tick()
expect_eq(update_error(), UPDATE_ERR_DOWNLOAD)
test "a failed download stops it" (base_st: mut BaseState, update_st: mut UpdateState, updatetest_st: mut UpdatetestState) {
fresh(base_st, update_st, updatetest_st, true)
update_start(base_st, update_st)
updatetest_st.net_text = feed(true)
updatetest_st.net_answer = 1
update_tick(base_st, update_st)
updatetest_st.net_answer = 0
update_tick(base_st, update_st)
expect_eq(update_error(update_st), UPDATE_ERR_DOWNLOAD)
}
test "the delta chain: each fetched, checked and patched onto the last, then applied" {
fresh(true)
test "the delta chain: each fetched, checked and patched onto the last, then applied" (base_st: mut BaseState, update_st: mut UpdateState, updatetest_st: mut UpdatetestState) {
fresh(base_st, update_st, updatetest_st, true)
seed_base()
update_start()
net_text = feed(true)
net_answer = 1
update_tick()
update_tick()
update_start(base_st, update_st)
updatetest_st.net_text = feed(true)
updatetest_st.net_answer = 1
update_tick(base_st, update_st)
update_tick(base_st, update_st)
hash_is("AB1.1.0")
proc_code = 0
update_tick()
expect_eq(update_state(), UPDATE_PATCH)
expect(Text.contains(last_args(), "--old " + pkgs() + "/Game-1.0.0-osx-full.nupkg"))
expect(Text.contains(last_args(), "--output " + pkgs() + "/Game-1.1.0-osx-full.nupkg"))
updatetest_st.proc_code = 0
update_tick(base_st, update_st)
expect_eq(update_state(update_st), UPDATE_PATCH)
expect(Text.contains(last_args(updatetest_st), "--old " + pkgs() + "/Game-1.0.0-osx-full.nupkg"))
expect(Text.contains(last_args(updatetest_st), "--output " + pkgs() + "/Game-1.1.0-osx-full.nupkg"))
Fs.write_text(pkgs() + "/Game-1.1.0-osx-full.nupkg", "patched")
update_tick()
expect_eq(update_state(), UPDATE_FETCH)
expect_eq(update_index(), 1)
update_tick()
update_tick(base_st, update_st)
expect_eq(update_state(update_st), UPDATE_FETCH)
expect_eq(update_index(update_st), 1)
update_tick(base_st, update_st)
hash_is("AB1.2.0")
update_tick()
update_tick(base_st, update_st)
Fs.write_text(pkgs() + "/Game-1.2.0-osx-full.nupkg", "patched")
update_tick()
expect_eq(update_state(), UPDATE_APPLY)
expect(Text.contains(last_args(), "--package " + pkgs() + "/Game-1.2.0-osx-full.nupkg"))
update_tick(base_st, update_st)
expect_eq(update_state(update_st), UPDATE_APPLY)
expect(Text.contains(last_args(updatetest_st), "--package " + pkgs() + "/Game-1.2.0-osx-full.nupkg"))
}
test "a package already on disk at its size is checked, not fetched again" {
fresh(true)
update_start()
net_text = feed(true)
net_answer = 1
test "a package already on disk at its size is checked, not fetched again" (base_st: mut BaseState, update_st: mut UpdateState, updatetest_st: mut UpdatetestState) {
fresh(base_st, update_st, updatetest_st, true)
update_start(base_st, update_st)
updatetest_st.net_text = feed(true)
updatetest_st.net_answer = 1
Fs.write_text(pkgs() + "/Game-1.2.0-osx-full.nupkg", Text.repeat("x", 1000))
update_tick()
expect_eq(update_state(), UPDATE_CHECK)
update_tick(base_st, update_st)
expect_eq(update_state(update_st), UPDATE_CHECK)
}
}

View file

@ -1,99 +1,99 @@
# tick.ludic - once a frame while an update runs: whatever the step waits on, and the next step
export function update_tick() -> void {
update__rate_tick()
if update__state == UPDATE_FEED { update__feed_tick() }
else if update__state == UPDATE_FETCH { update__fetch_tick() }
else if update__state == UPDATE_CHECK { update__check_tick() }
else if update__state == UPDATE_PATCH { update__patch_tick() }
export function update_tick(base_st: mut BaseState, update_st: mut UpdateState) -> void {
update__rate_tick(update_st)
if update_st.update__state == UPDATE_FEED { update__feed_tick(base_st, update_st) }
else if update_st.update__state == UPDATE_FETCH { update__fetch_tick(base_st, update_st) }
else if update_st.update__state == UPDATE_CHECK { update__check_tick(base_st, update_st) }
else if update_st.update__state == UPDATE_PATCH { update__patch_tick(base_st, update_st) }
}
function update__feed_tick() -> void {
let st = UpdateNet.poll(update__h)
function update__feed_tick(base_st: mut BaseState, update_st: mut UpdateState) -> void {
let st = UpdateNet.poll(update_st.update__h)
if st < 0 { return }
var feed: Val = null
if st > 0 { feed = Json.parse(UpdateNet.text(update__h)) }
UpdateNet.free(update__h)
update__h = -1
if st > 0 { feed = Json.parse(UpdateNet.text(update_st.update__h)) }
UpdateNet.free(update_st.update__h)
update_st.update__h = -1
if feed == null {
update__fail(UPDATE_ERR_UNREACHABLE)
update__fail(base_st, update_st, UPDATE_ERR_UNREACHABLE)
return
}
if not update_plan(feed) {
update__fail(UPDATE_ERR_NOTHING)
if not update_plan(update_st, feed) {
update__fail(base_st, update_st, UPDATE_ERR_NOTHING)
return
}
update__fact(UPDATE_STARTED)
update__fetch(0)
update__fact(base_st, update_st, UPDATE_STARTED)
update__fetch(base_st, update_st, 0)
}
function update__fetch_tick() -> void {
let st = UpdateNet.poll(update__h)
function update__fetch_tick(base_st: mut BaseState, update_st: mut UpdateState) -> void {
let st = UpdateNet.poll(update_st.update__h)
if st < 0 { return }
UpdateNet.free(update__h)
update__h = -1
UpdateNet.free(update_st.update__h)
update_st.update__h = -1
if st == 0 {
Fs.remove(update__path(update__i))
update__fail(UPDATE_ERR_DOWNLOAD)
Fs.remove(update__path(update_st, update_st.update__i))
update__fail(base_st, update_st, UPDATE_ERR_DOWNLOAD)
return
}
update__check(update__i)
update__check(base_st, update_st, update_st.update__i)
}
# a package rebuilt from deltas is not compared with the full one's hash: patch re-assembles the
# archive, so the same files come out as different bytes
function update__check_tick() -> void {
let code = UpdateProc.poll(update__proc)
function update__check_tick(base_st: mut BaseState, update_st: mut UpdateState) -> void {
let code = UpdateProc.poll(update_st.update__proc)
if code < 0 { return }
UpdateProc.free(update__proc)
update__proc = -1
if code != 0 or update__hash_read() != Text.upper(update__sha[update__i]) {
Fs.remove(update__path(update__i))
update__fail(UPDATE_ERR_CORRUPT)
UpdateProc.free(update_st.update__proc)
update_st.update__proc = -1
if code != 0 or update__hash_read(update_st) != Text.upper(update_st.update__sha[update_st.update__i]) {
Fs.remove(update__path(update_st, update_st.update__i))
update__fail(base_st, update_st, UPDATE_ERR_CORRUPT)
return
}
update__done_bytes += update__size[update__i]
if update__delta[update__i] { update__patch(update__i) } else { update__apply(update__path(update__i)) }
update_st.update__done_bytes += update_st.update__size[update_st.update__i]
if update_st.update__delta[update_st.update__i] { update__patch(base_st, update_st, update_st.update__i) } else { update__apply(base_st, update_st, update__path(update_st, update_st.update__i)) }
}
function update__patch(i: int) -> void {
let out = update_packages() + update__sep() + update__full[i]
function update__patch(base_st: mut BaseState, update_st: mut UpdateState, i: int) -> void {
let out = update_packages(update_st) + update__sep() + update_st.update__full[i]
if Fs.exists(out) { Fs.remove(out) }
let args = new []string
push(args, "patch")
push(args, "--silent")
push(args, "--old")
push(args, update__old)
push(args, update_st.update__old)
push(args, "--delta")
push(args, update__path(i))
push(args, update__path(update_st, i))
push(args, "--output")
push(args, out)
update__proc = UpdateProc.spawn(update__updater(), args)
if update__proc < 0 {
update__fail(UPDATE_ERR_PATCH_START)
update_st.update__proc = UpdateProc.spawn(update__updater(update_st), args)
if update_st.update__proc < 0 {
update__fail(base_st, update_st, UPDATE_ERR_PATCH_START)
return
}
update__old = out
update__state = UPDATE_PATCH
update__step_t0 = UpdateWorld.now_ms()
update_st.update__old = out
update_st.update__state = UPDATE_PATCH
update_st.update__step_t0 = UpdateWorld.now_ms()
}
function update__patch_tick() -> void {
let code = UpdateProc.poll(update__proc)
function update__patch_tick(base_st: mut BaseState, update_st: mut UpdateState) -> void {
let code = UpdateProc.poll(update_st.update__proc)
if code < 0 { return }
UpdateProc.free(update__proc)
update__proc = -1
if code != 0 or not Fs.exists(update__old) {
update__fail(UPDATE_ERR_PATCH)
UpdateProc.free(update_st.update__proc)
update_st.update__proc = -1
if code != 0 or not Fs.exists(update_st.update__old) {
update__fail(base_st, update_st, UPDATE_ERR_PATCH)
return
}
if update__i + 1 < len(update__files) { update__fetch(update__i + 1) } else { update__apply(update__old) }
if update_st.update__i + 1 < len(update_st.update__files) { update__fetch(base_st, update_st, update_st.update__i + 1) } else { update__apply(base_st, update_st, update_st.update__old) }
}
# Velopack waits for this process to be gone, swaps the install and starts the new version; the
# game hears UPDATE_APPLYING and exits. On macOS neither the root nor the packages are where
# UpdateMac would look, so both are named.
function update__apply(package: string) -> void {
update__package = package
function update__apply(base_st: mut BaseState, update_st: mut UpdateState, package: string) -> void {
update_st.update__package = package
let args = new []string
push(args, "apply")
push(args, "--silent")
@ -103,15 +103,15 @@ function update__apply(package: string) -> void {
push(args, package)
if update__mac() {
push(args, "--rootDir")
push(args, update_root())
push(args, update_root(update_st))
push(args, "--packageDir")
push(args, update_packages())
push(args, update_packages(update_st))
}
update__proc = UpdateProc.spawn(update__updater(), args)
if update__proc < 0 {
update__fail(UPDATE_ERR_APPLY)
update_st.update__proc = UpdateProc.spawn(update__updater(update_st), args)
if update_st.update__proc < 0 {
update__fail(base_st, update_st, UPDATE_ERR_APPLY)
return
}
update__state = UPDATE_APPLY
update__fact(UPDATE_APPLYING)
update_st.update__state = UPDATE_APPLY
update__fact(base_st, update_st, UPDATE_APPLYING)
}