From 8954be6eb8c3cc0a286a84cac55ca72ff25157bf Mon Sep 17 00:00:00 2001
From: "C. McEnroe" <june@causal.agency>
Date: Thu, 9 Jul 2020 15:41:32 -0400
Subject: Implement basic networks list

---
 scooper.c | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 112 insertions(+), 17 deletions(-)

diff --git a/scooper.c b/scooper.c
index 291abb0..afbbda5 100644
--- a/scooper.c
+++ b/scooper.c
@@ -25,6 +25,7 @@
 #include <unistd.h>
 
 #include <kcgi.h>
+#include <kcgihtml.h>
 #include <sqlite3.h>
 
 #define SQL(...) #__VA_ARGS__
@@ -33,8 +34,18 @@ enum { DatabaseVersion = 4 };
 
 static sqlite3 *db;
 
+static struct {
+	sqlite3_stmt *networks;
+	sqlite3_stmt *contexts;
+	sqlite3_stmt *events;
+	sqlite3_stmt *search;
+} stmt;
+
 static void dbClose(void) {
-	// TODO: Finalize statements.
+	if (stmt.networks) sqlite3_finalize(stmt.networks);
+	if (stmt.contexts) sqlite3_finalize(stmt.contexts);
+	if (stmt.events) sqlite3_finalize(stmt.events);
+	if (stmt.search) sqlite3_finalize(stmt.search);
 	sqlite3_close(db);
 }
 
@@ -76,31 +87,115 @@ static const struct kvalid Keys[KeysLen] = {
 #undef X
 };
 
-static enum kcgi_err fail(struct kreq *req, enum khttp status) {
-	return khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[status])
-		|| khttp_head(
-			req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_PLAIN]
-		)
+static enum kcgi_err head(struct kreq *req, enum khttp http, enum kmime mime) {
+	return khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[http])
+		|| khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]);
+}
+
+static enum kcgi_err fail(struct kreq *req, enum khttp http) {
+	return head(req, http, KMIME_TEXT_PLAIN)
 		|| khttp_body(req)
-		|| khttp_puts(req, khttps[status])
+		|| khttp_puts(req, khttps[http])
 		|| khttp_putc(req, '\n');
 }
 
+static const char *stylesheet;
+
+static enum kcgi_err htmlHead(struct khtmlreq *html, const char *title) {
+	enum kcgi_err error = khtml_elem(html, KELEM_DOCTYPE)
+		|| khtml_attr(html, KELEM_META, KATTR_CHARSET, "utf-8", KATTR__MAX)
+		|| khtml_elem(html, KELEM_TITLE)
+		|| khtml_puts(html, title)
+		|| khtml_closeelem(html, 1);
+	if (error) return error;
+	if (stylesheet) {
+		error = khtml_attr(
+			html, KELEM_LINK,
+			KATTR_REL, "stylesheet",
+			KATTR_HREF, stylesheet,
+			KATTR__MAX
+		);
+		if (error) return error;
+	}
+	return khtml_elem(html, KELEM_H1)
+		|| khtml_puts(html, title)
+		|| khtml_closeelem(html, 1);
+}
+
+static const char *NetworksQuery = SQL(
+	SELECT DISTINCT network
+	FROM contexts
+	ORDER BY network;
+);
+
+static enum kcgi_err networks(struct kreq *req) {
+	struct khtmlreq html;
+	enum kcgi_err error = head(req, KHTTP_200, KMIME_TEXT_HTML)
+		|| khttp_body(req)
+		|| khtml_open(&html, req, KHTML_PRETTY)
+		|| htmlHead(&html, "Networks")
+		|| khtml_elem(&html, KELEM_UL);
+	if (error) goto fail;
+
+	int result;
+	while (SQLITE_ROW == (result = sqlite3_step(stmt.networks))) {
+		const char *network = sqlite3_column_text(stmt.networks, 0);
+		char *href = khttp_urlpart(
+			NULL, NULL, Pages[Contexts], Keys[Network].name, network, NULL
+		);
+		if (!href) err(EX_OSERR, "khttp_urlpart");
+
+		error = khtml_elem(&html, KELEM_LI)
+			|| khtml_attr(&html, KELEM_A, KATTR_HREF, href, KATTR__MAX)
+			|| khtml_puts(&html, network)
+			|| khtml_closeelem(&html, 2);
+		free(href);
+		if (error) goto fail;
+	}
+	if (result != SQLITE_DONE) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db));
+
+	error = khtml_close(&html);
+
+fail:
+	sqlite3_reset(stmt.networks);
+	return error;
+}
+
+static enum kcgi_err contexts(struct kreq *req) {
+	return KCGI_OK;
+}
+
+static enum kcgi_err events(struct kreq *req) {
+	return KCGI_OK;
+}
+
+static enum kcgi_err search(struct kreq *req) {
+	return KCGI_OK;
+}
+
 static enum kcgi_err request(struct kreq *req) {
 	if (req->method != KMETHOD_HEAD && req->method != KMETHOD_GET) {
 		return fail(req, KHTTP_405);
 	}
-	if (req->mime != KMIME_TEXT_HTML || req->page == PagesLen) {
-		return fail(req, KHTTP_404);
+	switch (req->page) {
+		case Networks: return networks(req);
+		case Contexts: return contexts(req);
+		case Events:   return events(req);
+		case Search:   return search(req);
+		default:       return fail(req, KHTTP_404);
 	}
+}
 
-	return KCGI_OK;
+static void prepare(sqlite3_stmt **stmt, const char *query) {
+	int error = sqlite3_prepare_v3(
+		db, query, -1, SQLITE_PREPARE_PERSISTENT, stmt, NULL
+	);
+	if (error) errx(EX_SOFTWARE, "%s: %s", sqlite3_errmsg(db), query);
 }
 
 int main(int argc, char *argv[]) {
 	bool fastCGI = false;
 	bool public = false;
-	const char *stylesheet = NULL;
 
 	for (int opt; 0 < (opt = getopt(argc, argv, "fps:"));) {
 		switch (opt) {
@@ -116,22 +211,22 @@ int main(int argc, char *argv[]) {
 	if (error) errx(EX_NOINPUT, "%s: %s", argv[optind], sqlite3_errmsg(db));
 	atexit(dbClose);
 
-	sqlite3_stmt *stmt;
+	sqlite3_stmt *check;
 	error = sqlite3_prepare_v2(
-		db, SQL(PRAGMA user_version;), -1, &stmt, NULL
+		db, SQL(PRAGMA user_version;), -1, &check, NULL
 	);
 	if (error) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db));
 
-	error = sqlite3_step(stmt);
+	error = sqlite3_step(check);
 	if (error != SQLITE_ROW) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db));
 
-	int version = sqlite3_column_int(stmt, 0);
+	int version = sqlite3_column_int(check, 0);
 	if (version != DatabaseVersion) {
 		errx(EX_DATAERR, "unsupported database version %d", version);
 	}
-	sqlite3_finalize(stmt);
+	sqlite3_finalize(check);
 
-	// TODO: Prepare all statements with persist flag.
+	prepare(&stmt.networks, NetworksQuery);
 	
 	if (fastCGI) {
 		struct kfcgi *fcgi;
-- 
cgit 1.4.1

mit/bin/order.y?id=27fef8b782291fb68b643d67bdc2cc0757290591&amp;follow=1'>Support simple assignment in order</a></td><td>June McEnroe</td></tr>
<tr class='nohover-highlight'><td/><td colspan='3' class='logmsg'>
</td></tr>
<tr class='logheader'><td><span title='2019-05-15 19:54:29 -0400'>2019-05-15</span></td><td class='logsubject'><a href='/src/commit/bin/order.y?id=9e9b9ff853995a4478747f6c12e00c01cbb34705&amp;follow=1'>Implement sizeof in order</a></td><td>June McEnroe</td></tr>
<tr class='nohover-highlight'><td/><td colspan='3' class='logmsg'>
</td></tr>
<tr class='logheader'><td><span title='2019-05-15 19:33:38 -0400'>2019-05-15</span></td><td class='logsubject'><a href='/src/commit/bin/order.y?id=cdf925f29e53eba20bc47af161fb6aec3b7c465d&amp;follow=1'>Add order</a></td><td>June McEnroe</td></tr>
<tr class='nohover-highlight'><td/><td colspan='3' class='logmsg'>
</td></tr>
<tr class='logheader'><td><span title='2019-05-12 12:18:31 -0400'>2019-05-12</span></td><td class='logsubject'><a href='/src/commit/bin/bit.y?id=51ee06fc5f2383c64e3afdce68ebfe49625ac7e4&amp;follow=1'>Add T suffix in bit</a></td><td>June McEnroe</td></tr>
<tr class='nohover-highlight'><td/><td colspan='3' class='logmsg'>
</td></tr>
<tr class='logheader'><td><span title='2019-05-10 17:33:36 -0400'>2019-05-10</span></td><td class='logsubject'><a href='/src/commit/bin/hi.c?id=0a322cd0b39aa73f9b15fe99faaffaa7ac115ee0&amp;follow=1'>Highlight yacc and lex files as C</a></td><td>June McEnroe</td></tr>
<tr class='nohover-highlight'><td/><td colspan='3' class='logmsg'>
Their %-prefixed directives should probably be highlighted Macro.


</td></tr>
<tr class='logheader'><td><span title='2019-05-10 16:29:50 -0400'>2019-05-10</span></td><td class='logsubject'><a href='/src/commit/bin/hi.c?id=bb9a412a8c9736dc52fe9a7aee997ae5a6c582d8&amp;follow=1'>Use val instead of suboptarg</a></td><td>June McEnroe</td></tr>
<tr class='nohover-highlight'><td/><td colspan='3' class='logmsg'>
suboptarg doesn't exist in GNU. Hopefully BSD getsubopt also sets val on
failure?


</td></tr>
<tr class='logheader'><td><span title='2019-05-09 22:37:41 -0400'>2019-05-09</span></td><td class='logsubject'><a href='/src/commit/txt/books.txt?id=0b94d9fd6c9a2e8c59ef7fd88d2b1c78b5211e87&amp;follow=1'>Add Parable of the Sower</a></td><td>June McEnroe</td></tr>
<tr class='nohover-highlight'><td/><td colspan='3' class='logmsg'>
</td></tr>
<tr class='logheader'><td><span title='2019-05-07 22:40:21 -0400'>2019-05-07</span></td><td class='logsubject'><a href='/src/commit/bin/bitlex.l?id=f4b7d09ddb35f014dc12e10ce4ed9f36fb67550a&amp;follow=1'>Add bit without build</a></td><td>June McEnroe</td></tr>
<tr class='nohover-highlight'><td/><td colspan='3' class='logmsg'>
Need to do some stuff in the Makefile for lex and yacc and generating
HTML pages for it.


</td></tr>
<tr class='logheader'><td><span title='2019-05-04 17:58:11 -0400'>2019-05-04</span></td><td class='logsubject'><a href='/src/commit/bin/Makefile?id=7f312726e016fbfcd0725c5b9feb625503ec5f67&amp;follow=1'>Fix MANDIR typo</a></td><td>June McEnroe</td></tr>
<tr class='nohover-highlight'><td/><td colspan='3' class='logmsg'>
</td></tr>
<tr class='logheader'><td><span title='2019-05-04 17:50:38 -0400'>2019-05-04</span></td><td class='logsubject'><a href='/src/commit/bin/relay.c?id=b6d2f4a5ab6089cbcf8f2e1de40d292baa5b3a83&amp;follow=1'>Move relay to bin</a></td><td>June McEnroe