use fmt; use log; use os; use net; use ev; use unix::signal; use io; use memio; use encoding::utf8; use strings; use bytes; use shlex; use time; use getopt; use dirs; use path; type server = struct { status: *status, loop: *ev::loop, sock: *ev::file, clients: []*client, socketpath: str, exit: int, }; type client = struct { server: *server, sock: *ev::file, buf: [os::BUFSZ]u8, wbuf: []u8, }; type status = struct { clock: clock, host: hostname, generics: []generic, }; type section = struct { label: str, }; type hostname = struct { section: section, value: str, }; export fn main() void = { const cmd = getopt::parse(os::args, "status daemon", ('s', "path", "unix socket location") ); defer getopt::finish(&cmd); let socketpath = path::string(&path::init(dirs::runtime()!, "statrepsocket")!); log::println(socketpath); for (let opt .. cmd.opts) { switch (opt.0) { case 's' => socketpath = opt.1; case => abort(); }; }; const loop = ev::newloop()!; defer ev::finish(&loop); const sock = ev::listen_unix(&loop, socketpath)!; defer ev::close(sock); let status = newstatus()!; defer finishstatus(&status); let state = server { status = &status, loop = &loop, sock = sock, socketpath = socketpath, ... }; const clocktimer = ev::newtimer(&loop, &clocktimerf, time::clock::MONOTONIC)!; ev::timer_configure(clocktimer, 1 * time::SECOND, 1 * time::SECOND); ev::setuser(clocktimer, &state); ev::setuser(sock, &state); ev::accept(sock, &accept); const sig = ev::signal(&loop, &signal, signal::sig::INT, signal::sig::TERM)!; ev::setuser(sig, &state); defer ev::close(sig); for (ev::dispatch(&loop, -1)!) void; os::exit(state.exit); }; fn newstatus() (status | io::error | utf8::invalid) = { return status { clock = buildclock()?, host = buildhostname(), generics = alloc([ buildgeneric("/home/jhurst/.local/share/statusbar/vol.sh"), ]), }; }; fn finishstatus(status: *status) void = { finishclock(&status.clock); for (let gen &.. status.generics) { finishgeneric(gen); }; free(status.generics); }; fn buildhostname() hostname = { return hostname { section = section { label = "hostname: ", }, value = os::hostname(), }; }; fn printstatus(state: *status) str = { const s = getstatus(state); defer free(s); fmt::println(strings::trim(s))!; return s; }; // Must free return value fn getstatus(state: *status) str = { const st = memio::dynamic(); defer io::close(&st)!; for (let gen .. state.generics) { memio::concat(&st, gen.value)!; memio::concat(&st, " | ")!; }; memio::concat(&st, state.host.section.label, state.host.value, " | ", state.clock.section.label, state.clock.value, "\n" )!; //const s = strings::concat( // state.generic.section.label, state.generic.value, " | ", // state.host.section.label, state.host.value, " | ", // state.clock.section.label, state.clock.value, "\n"); const s = strings::dup(memio::string(&st)!); return s; }; fn accept(sock: *ev::file, result: (*ev::file | net::error)) void = { let server = ev::getuser(sock): *server; const sock = match (result) { case let sock: *ev::file => yield sock; case let err: net::error => log::printfln("Error: accept: {}", net::strerror(err)); ev::stop(server.loop); server.exit = 1; return; }; log::println("accepted"); const client = alloc(client { server = server, sock = sock, ... }); append(server.clients, client); ev::setuser(client.sock, client); ev::read(client.sock, &read, client.buf); ev::accept(server.sock, &accept); }; fn execmd(s: *status, cmd: []str) (void | str) = { switch (cmd[0]) { case "print" => printstatus(s); case "status" => return getstatus(s); case "clock" => updateclock(&s.clock)!; case "generic" => for (let gen &.. s.generics) { updategeneric(gen)!; }; case => assert(false); }; }; fn write(file: *ev::file, result: (size | io::error)) void = { const client = ev::getuser(file): *client; const n = match (result) { case let err: io::error => log::printfln("Error: write: {}", io::strerror(err)); client_close(client); return; case let n: size => yield n; }; static delete(client.wbuf[..n]); if (len(client.wbuf) != 0) { ev::write(client.sock, &write, client.wbuf); } else { ev::read(client.sock, &read, client.buf); }; }; fn read(file: *ev::file, result: (size | io::EOF | io::error)) void = { const client = ev::getuser(file): *client; const n = match (result) { case let err: io::error => log::printfln("Error: read: {}", io::strerror(err)); client_close(client); return; case io::EOF => client_close(client); return; case let n: size => yield n; }; const b = client.buf[..n]; if (!bytes::contains(b, strings::toutf8("\n"))) { ev::read(client.sock, &read, client.buf); } else { const s = match (strings::fromutf8(b)) { case let s: str => yield s; case let e: utf8::invalid => log::printfln("Error: read: {}", utf8::strerror(e)); client_close(client); return; }; const i = strings::index(s, "\n") as size; const cmd = strings::sub(s, 0z, i); log::println(cmd); const spl = match (shlex::split(cmd)) { case let e: shlex::syntaxerr => log::printfln("Error: read: {}", shlex::strerror(e)); client_close(client); return; case let spl: []str => yield spl; }; defer strings::freeall(spl); match (execmd(client.server.status, spl)) { case let s: str => defer free(s); log::println(strings::trim(s)); append(client.wbuf, strings::toutf8(s)...); case => void; }; if (len(client.wbuf) != 0) { ev::write(client.sock, &write, client.wbuf); } else { ev::read(client.sock, &read, client.buf); }; }; }; fn client_close(client: *client) void = { const server = client.server; for (let i = 0z; i < len(server.clients); i += 1) { if (server.clients[i] == client) { delete(server.clients[i]); break; }; }; log::println("Connection closed"); ev::close(client.sock); free(client); }; fn signal(file: *ev::file, sig: signal::sig) void = { let server = ev::getuser(file): *server; log::printfln("Exiting due to {}", signal::signame(sig)); const l = ev::getloop(file); ev::stop(l); os::remove(server.socketpath)!; };