136 lines
5 KiB
Lua
136 lines
5 KiB
Lua
-- This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ --
|
|
--* .name Asynchronous callbacks for anything that needs it
|
|
local C = require("c")
|
|
local X = require("x")
|
|
local Conf = require("conf")
|
|
local ev = require("ev") -- https://github.com/brimworks/lua-ev
|
|
local Async = {ev = ev}
|
|
|
|
--* fd(int fd, int events, function callback) Raw file descriptor callbacks
|
|
function Async.fd(fd, evs, cb)
|
|
ev.IO.new(cb, fd, evs):start(ev.Loop.default)
|
|
end
|
|
function Async.idle(cb)
|
|
local id
|
|
id = ev.Idle.new(function()
|
|
id:stop(ev.Loop.default)
|
|
cb()
|
|
X.sync()
|
|
end)
|
|
id:start(ev.Loop.default)
|
|
end
|
|
|
|
--* timer(float after, [float repeat], function callback) Calls **callback** after **after** seconds, and then repeats on a **repeat** second loop if it's provided
|
|
function Async.timer(after, rep, cb)
|
|
if not cb then rep, cb = cb, rep end
|
|
if after <= 0 then after = 0.00000001 end
|
|
ev.Timer.new(cb, after, rep):start(ev.Loop.default)
|
|
end
|
|
|
|
--* .desc The *spawn** functions return a table with the **pid**, as well as *write*(*str* **data**) and *close*() for stdin
|
|
local StdinWrapper = {
|
|
write = function(self, text)
|
|
C.write(self.stdin, text)
|
|
return self
|
|
end,
|
|
close = function(self)
|
|
C.close(self.stdin)
|
|
end
|
|
}
|
|
StdinWrapper.__index = StdinWrapper
|
|
--* table stdin spawn(str program, [str arg, ...], [function callback]) Spawns a program and calls **callback** once it exits. Parameters passed to **callback** are (*str* **stdout**, *str* **stderr**, *int* **exitStatus**)
|
|
function Async.spawn(...)
|
|
local args = {...}
|
|
local cb
|
|
if #args == 0 then error("Need a program") end
|
|
if type(args[#args]) == "function" then cb = table.remove(args, #args) end
|
|
local pid, stdin, stdout, stderr
|
|
if cb then
|
|
pid, stdin, stdout, stderr = C.spawn(args)
|
|
if not pid then return nil, "Failed to spawn" end
|
|
local childWatcher
|
|
childWatcher = ev.Child.new(function(loop, child)
|
|
childWatcher:stop(loop)
|
|
local out = C.read(stdout)
|
|
local err = C.read(stderr)
|
|
C.close(stdin, stdout, stderr)
|
|
cb(out, err, child:getstatus().exit_status)
|
|
end, pid, false)
|
|
childWatcher:start(ev.Loop.default)
|
|
else
|
|
pid, stdin, stdout, stderr = C.spawn(args)
|
|
if not pid then return nil, "Failed to spawn" end
|
|
end
|
|
return setmetatable({ pid = pid, stdin = stdin }, StdinWrapper)
|
|
end
|
|
--* table stdin spawnShell(str command, [str shell], [function callback]) Runs **command** in a shell instace. The shell is the first of **shell**, **Conf.shell**, **$SHELL**, "/bin/sh". Parameters passed to **callback** are the same as with *spawn*
|
|
function Async.spawnShell(str, shell, cb)
|
|
if not cb then cb, shell = shell, cb end
|
|
return Async.spawn(shell or Conf.shell or os.getenv("SHELL") or "/bin/sh", "-c", str, cb)
|
|
end
|
|
--* table stdin spawnRead(str program, [str arg, ...], function dataCallback, [function exitCallback]) Spawns a program and calls **dataCallback** when the program writes to stdout or stderr, then **exitCallback** once it exits. Parameters to **dataCallback** are (*str* **stdout**, *str* **stderr**). Paramaters to **exitCallback** are (*int* **exitStatus**)
|
|
function Async.spawnRead(...)
|
|
local args = {...}
|
|
if #args < 2 then error("Need a program and callback") end
|
|
local outcb = table.remove(args, #args)
|
|
if type(outcb) ~= "function" then error("Need a callback") end
|
|
local exitcb
|
|
if type(args[#args]) == "function" then
|
|
if #args < 2 then error("Need a program") end
|
|
outcb, exitcb = table.remove(args, #args), outcb
|
|
if type(outcb) ~= "function" then error("Need a callback") end
|
|
end
|
|
local pid, stdin, stdout, stderr = C.spawn(args)
|
|
C.setNonBlocking(stdout)
|
|
C.setNonBlocking(stderr)
|
|
local stdoutWatcher = ev.IO.new(function(loop, io, revents)
|
|
local d = C.read(stdout)
|
|
print(d, #d)
|
|
if d and #d > 0 then
|
|
outcb(d, nil)
|
|
end
|
|
end, stdout, ev.READ)
|
|
stdoutWatcher:start(ev.Loop.default)
|
|
local exitWatcher
|
|
exitWatcher = ev.Child.new(function(loop, child, revents)
|
|
local out = C.read(stdout)
|
|
local err = C.read(stderr)
|
|
C.close(stdout, stderr, stdin)
|
|
stdoutWatcher:stop(loop)
|
|
exitWatcher:stop(loop)
|
|
outcb(out, err)
|
|
if exitcb then exitcb(child:getstatus().exit_status) end
|
|
end, pid, false)
|
|
exitWatcher:start(ev.Loop.default)
|
|
return setmetatable({pid = pid, stdin = stdin}, StdinWrapper)
|
|
end
|
|
--* table stdin spawnReadShell(str command, [str shell], function dataCallback, [function exitCallback]) Combination of *spawnRead* and *spawnShell*
|
|
function Async.spawnReadShell(str, shell, outcb, exitcb)
|
|
if type(shell) == "function" then
|
|
shell, outcb, exitcb = nil, shell, outcb
|
|
end
|
|
return Async.spawnRead(shell or Conf.shell or os.getenv("SHELL") or "/bin/sh", "-c", str, outcb, exitcb)
|
|
end
|
|
function Async.readFile(path, cb)
|
|
local stdout = C.getFD(assert(io.open(path, "r")))
|
|
C.setNonBlocking(stdout)
|
|
local chunks = {}
|
|
local stdoutWatcher
|
|
stdoutWatcher = ev.IO.new(function()
|
|
local c = C.read(stdout)
|
|
if c and #c > 0 then
|
|
table.insert(chunks, c)
|
|
else
|
|
C.close(stdout)
|
|
stdoutWatcher:stop(ev.Loop.default)
|
|
cb(table.concat(chunks))
|
|
end
|
|
end, stdout, ev.READ)
|
|
stdoutWatcher:start(ev.Loop.default)
|
|
end
|
|
|
|
function Async.loop()
|
|
ev.Loop.default:loop()
|
|
end
|
|
|
|
return Async
|