mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-14 01:11:07 +02:00
123 lines
4.8 KiB
Lua
123 lines
4.8 KiB
Lua
-- Worker thread behind src/net/Fetch.lua. Several of these run as a pool.
|
|
--
|
|
-- Pulls jobs off the shared "fetch_cmd" channel and pushes results onto
|
|
-- "fetch_result". Every job is wrapped in pcall: a worker that dies takes
|
|
-- its in-flight job with it, and Fetch surfaces that as an error rather than
|
|
-- leaving a loader overlay spinning forever.
|
|
--
|
|
-- Transport is HostShell, so this inherits the platform matrix that already
|
|
-- exists (curl on desktop, the JNI bridge on Android). Fresh love threads do
|
|
-- not carry the "src.*" package searcher, so HostShell is pulled in with
|
|
-- love.filesystem.load exactly like src/update/check_worker.lua does.
|
|
|
|
require("love.thread")
|
|
require("love.filesystem")
|
|
require("love.timer")
|
|
require("love.system")
|
|
|
|
local function loadModule(path)
|
|
local ok, chunk = pcall(love.filesystem.load, path)
|
|
if not ok or type(chunk) ~= "function" then return nil end
|
|
local ok2, mod = pcall(chunk)
|
|
if not ok2 then return nil end
|
|
return mod
|
|
end
|
|
|
|
local HostShell = loadModule("src/core/HostShell.lua")
|
|
|
|
local cmdCh = love.thread.getChannel("fetch_cmd")
|
|
local resCh = love.thread.getChannel("fetch_result")
|
|
-- Raised by Fetch.shutdown BEFORE the wake sentinels go out. A worker checks
|
|
-- it after every demand() and drops whatever it just pulled, so a quit does
|
|
-- not have to wait its turn behind a queue of jobs nobody will ever read the
|
|
-- results of.
|
|
local quitCh = love.thread.getChannel("fetch_quit")
|
|
|
|
local saveDir = love.filesystem.getSaveDirectory()
|
|
|
|
-- See the note in doGet: these bound how long a quit can block. A mod index
|
|
-- or a release list is a small JSON document, and a mod zip is a few MB; the
|
|
-- old 300s download ceiling was sized for the self-updater's whole payload,
|
|
-- which does not come through this pool. Callers may pass a shorter one
|
|
-- (job.maxSeconds) -- a thumbnail has no business holding the process open
|
|
-- for as long as a mod install does.
|
|
local GET_MAX_SECONDS = 20
|
|
local DOWNLOAD_MAX_SECONDS = 90
|
|
|
|
local function quitting()
|
|
return quitCh:peek() ~= nil
|
|
end
|
|
|
|
local function post(t) resCh:push(t) end
|
|
|
|
local function doGet(job)
|
|
if not HostShell then
|
|
post({ id = job.id, ok = false, err = "no transport" })
|
|
return
|
|
end
|
|
-- Bounded transfer time: a worker inside a blocking curl cannot see a quit
|
|
-- command, and LOVE waits for live threads before exiting (#339), so this
|
|
-- ceiling is also the worst case for how long closing the window can take.
|
|
local body, err = HostShell.httpGet(job.url, job.userAgent, job.accept,
|
|
tonumber(job.maxSeconds) or GET_MAX_SECONDS)
|
|
if not body then
|
|
post({ id = job.id, ok = false, err = err or "fetch failed" })
|
|
return
|
|
end
|
|
post({ id = job.id, ok = true, body = body })
|
|
end
|
|
|
|
-- Downloads go straight to the save directory. HostShell.httpDownload
|
|
-- blocks until curl exits, which is fine here -- this is the whole reason
|
|
-- the work is on a worker -- but it means progress cannot be sampled from
|
|
-- inside the call. Where the caller knows the expected size we poll the
|
|
-- growing file from a second pass instead; where it does not, the job simply
|
|
-- reports indeterminate progress and the UI shows a spinner.
|
|
local function doDownload(job)
|
|
if not HostShell then
|
|
post({ id = job.id, ok = false, err = "no transport" })
|
|
return
|
|
end
|
|
local rel = job.dest
|
|
local abs = saveDir .. "/" .. rel
|
|
local dir = rel:match("^(.*)/[^/]*$")
|
|
if dir then love.filesystem.createDirectory(dir) end
|
|
love.filesystem.remove(rel)
|
|
|
|
local ok, err = HostShell.httpDownload(job.url, abs, job.userAgent,
|
|
job.accept, tonumber(job.maxSeconds) or DOWNLOAD_MAX_SECONDS)
|
|
if not ok then
|
|
post({ id = job.id, ok = false, err = err or "download failed" })
|
|
return
|
|
end
|
|
local info = love.filesystem.getInfo(rel)
|
|
if not info or (info.size or 0) == 0 then
|
|
love.filesystem.remove(rel)
|
|
post({ id = job.id, ok = false, err = "empty download" })
|
|
return
|
|
end
|
|
post({ id = job.id, ok = true, path = rel, done = true })
|
|
end
|
|
|
|
while true do
|
|
local job = cmdCh:demand()
|
|
-- The flag is checked before the job's KIND, so a worker woken by a
|
|
-- sentinel abandons whatever real job it happened to pull instead of
|
|
-- running it. Without this the quit commands queued behind a page of
|
|
-- thumbnail downloads and closing the window blocked for as long as those
|
|
-- transfers took -- the launcher froze on close after a visit to the mods
|
|
-- tabs, which is exactly what LOVE waiting on live threads looks like.
|
|
if quitting() then break end
|
|
if type(job) == "table" then
|
|
if job.kind == "quit" then
|
|
break
|
|
elseif job.kind == "get" then
|
|
local ok, err = pcall(doGet, job)
|
|
if not ok then post({ id = job.id, ok = false, err = tostring(err) }) end
|
|
elseif job.kind == "download" then
|
|
local ok, err = pcall(doDownload, job)
|
|
if not ok then post({ id = job.id, ok = false, err = tostring(err) }) end
|
|
end
|
|
end
|
|
end
|