diff options
-rw-r--r-- | scooper.c | 129 |
1 files 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; |