summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Hurst <julian.hurst@digdash.com>2024-10-22 18:37:21 +0200
committerJulian Hurst <julian.hurst@digdash.com>2024-10-22 18:37:21 +0200
commitaa5a442284b2caf09cfbad0bf79756da757e5179 (patch)
treec0561abaf8a855452b2bcfe58a8a1395ea3e975c
downloadstatusdaemon-aa5a442284b2caf09cfbad0bf79756da757e5179.tar.gz
Initial commit
-rw-r--r--cmd/statrep/clock.ha46
-rw-r--r--cmd/statrep/generic.ha41
-rw-r--r--cmd/statrep/main.ha288
3 files changed, 375 insertions, 0 deletions
diff --git a/cmd/statrep/clock.ha b/cmd/statrep/clock.ha
new file mode 100644
index 0000000..f1e65fb
--- /dev/null
+++ b/cmd/statrep/clock.ha
@@ -0,0 +1,46 @@
+use time::date;
+use memio;
+use strings;
+use encoding::utf8;
+use io;
+use ev;
+
+type clock = struct {
+ section: section,
+ value: str,
+};
+
+fn buildclock() (clock | io::error | utf8::invalid) = {
+ const d = date::now();
+ let s = memio::dynamic();
+ defer io::close(&s)!;
+ date::format(&s, "%A %F %T", &d)?;
+ const val = strings::dup(memio::string(&s)?);
+ return clock {
+ section = section {
+ label = "clock: ",
+ },
+ value = val,
+ };
+};
+
+fn updateclock(clock: *clock) (void | io::error | utf8::invalid) = {
+ const d = date::now();
+ let s = memio::dynamic();
+ defer io::close(&s)!;
+ date::format(&s, "%A %F %T", &d)?;
+ const val = strings::dup(memio::string(&s)?);
+ free(clock.value);
+ clock.value = val;
+};
+
+fn finishclock(clock: *clock) void = {
+ free(clock.value);
+};
+
+fn clocktimerf(file: *ev::file) void = {
+ let server = ev::getuser(file): *server;
+ updateclock(&server.status.clock)!;
+ printstatus(server.status);
+};
+
diff --git a/cmd/statrep/generic.ha b/cmd/statrep/generic.ha
new file mode 100644
index 0000000..fb61ab0
--- /dev/null
+++ b/cmd/statrep/generic.ha
@@ -0,0 +1,41 @@
+use os::exec;
+use shlex;
+use strings;
+use io;
+use os;
+
+type generic = struct {
+ section: section,
+ cmd: str,
+ value: str,
+};
+
+fn buildgeneric(cmd: str) generic = {
+ let gen = generic {
+ section = section {
+ label = "generic: ",
+ },
+ cmd = cmd,
+ ...
+ };
+ updategeneric(&gen)!;
+ return gen;
+};
+
+fn updategeneric(gen: *generic) (void | shlex::syntaxerr | exec::error) = {
+ const spl = shlex::split(gen.cmd)?;
+ const cmd = exec::cmd(spl[0], spl[1..]...)?;
+ let pipe = exec::pipe();
+ exec::addfile(&cmd, os::stdout_file, pipe.1);
+ let proc = exec::start(&cmd)?;
+ io::close(pipe.1)!;
+ let data = io::drain(pipe.0)!;
+ defer free(data);
+ io::close(pipe.0)!;
+ exec::wait(&proc)!;
+ gen.value = strings::dup(strings::trim(strings::fromutf8(data)!));
+};
+
+fn finishgeneric(gen: *generic) void = {
+ free(gen.value);
+};
diff --git a/cmd/statrep/main.ha b/cmd/statrep/main.ha
new file mode 100644
index 0000000..aa174b7
--- /dev/null
+++ b/cmd/statrep/main.ha
@@ -0,0 +1,288 @@
+use fmt;
+use log;
+use os;
+use net;
+use ev;
+use unix::signal;
+use io;
+use memio;
+use time::date;
+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)!;
+};