diff --git a/xurfer/ext/unixy/main.lua b/xurfer/ext/unixy/main.lua new file mode 100644 index 0000000..11ab7e4 --- /dev/null +++ b/xurfer/ext/unixy/main.lua @@ -0,0 +1,111 @@ +-- The unixy extension allows proxying api-functions through cli apps. +-- Why? +-- Sometimes the host OS already has cli-apps which can make up for missing lua functionality. +-- The unixy extension is written in such way that it does not block the mainloop too much. +-- Instead it checks whether the PID still exists at a certain interval-seconds (dtmax) +-- +-- example of proxying an api-function to a cli app (curl): +-- +-- api.protocol.http.request = unixy.proxyCLI( +-- function(url,opts, cb) return string.format( "curl -v -s %s", url ) end, +-- function( stdout, stderr, retcode, url, opts, cb) +-- local status = 200 +-- if retcode ~= 0 then status = retcode end +-- cb( retcode, stdout, {string = stderr}) -- todo: parse string into key/values +-- end +-- ) +-- +-- api.protocol.http.request( "https://2wa.isvery.ninja", {}, function(status,data,headers) +-- print_r(status) +-- print_r(headers) +-- print_r(data) +-- end) +-- +local api = ... +local ecs = api.ecs + +if table.unpack == nil then + table.unpack = unpack +end + +local unixy +unixy = { + name = "unixy", + enabled = true, + jobs = {}, + dtmax = 1, + dt = 0, + + init = function() + end, + + proxyCLI = function( cmdfunc, callback ) + return function(...) + return unixy.addjob( cmdfunc, callback, ...) + end + end, + + update = function(dt) + unixy.dt = unixy.dt + dt + if unixy.dt > unixy.dtmax then + unixy.dt = 0 + if #unixy.jobs > 0 then + unixy.check_jobs() + end + end + end, + + addjob = function( cmdgen, callback, ...) + local stdout_file = os.tmpname() + local stderr_file = os.tmpname() + local pid_file = os.tmpname() + + -- Run curl in the background (&) + Save curl's PID ($!) to a file + local cmd = cmdgen( ... ) + cmd = string.format( cmd .. " 1> %s 2> %s & echo $! > %s", stdout_file, stderr_file, pid_file) + os.execute(cmd) + + -- Read the PID that was generated + local f = io.open(pid_file, "r") + local pid = f:read("*a"):gsub("%s+", "") + f:close() + os.remove(pid_file) -- clean up pid file immediately + + table.insert(unixy.jobs, { + pid = pid, + args = {...}, + stdout_file = stdout_file, + stderr_file = stderr_file, + callback = callback + }) + trace("[unixy] start job with pid " .. pid .. " => 1> " .. stdout_file .. " 2> " .. stderr_file) + print_r(unixy.jobs) + end, + + check_jobs = function() + for i = #unixy.jobs, 1, -1 do + local job = unixy.jobs[i] + -- 'kill -0' doesn't kill the process, it just checks if it exists. + local retcode = os.execute("ps -p " .. job.pid .. " > /dev/null") + if retcode ~= 0 then + -- Process finished! Read stdout & stderr + local f = io.open(job.stdout_file, "r") + local stdout = f:read("*a") + f:close() + f = io.open(job.stderr_file, "r") + local stderr = f:read("*a") + f:close() + -- Clean up + os.remove(job.stdout_file) + os.remove(job.stderr_file) + job.callback(stdout, stderr, retcode, unpack(job.args) ) + table.remove(unixy.jobs, i) + else + io.write(".") + io.flush() + end + end + end, +} + +return unixy