diff options
author | June McEnroe <june@causal.agency> | 2020-07-09 18:57:29 -0400 |
---|---|---|
committer | June McEnroe <june@causal.agency> | 2020-07-09 18:57:29 -0400 |
commit | 7b521cf75376ccfd38c2ff077c854b408fe616ff (patch) | |
tree | 4e510cf548fc02e9a7cbbf6bc209259d5ec06428 /scooper.c | |
parent | Implement basic contexts listing (diff) | |
download | scooper-7b521cf75376ccfd38c2ff077c854b408fe616ff.tar.gz scooper-7b521cf75376ccfd38c2ff077c854b408fe616ff.zip |
Split code and add breadcrumb nav
Diffstat (limited to 'scooper.c')
-rw-r--r-- | scooper.c | 319 |
1 files changed, 0 insertions, 319 deletions
diff --git a/scooper.c b/scooper.c deleted file mode 100644 index 2c7b58a..0000000 --- a/scooper.c +++ /dev/null @@ -1,319 +0,0 @@ -/* Copyright (C) 2020 C. McEnroe <june@causal.agency> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <err.h> -#include <stdarg.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <sys/types.h> -#include <sysexits.h> -#include <unistd.h> - -#include <kcgi.h> -#include <kcgihtml.h> -#include <sqlite3.h> - -#if KCGI_VMAJOR == 0 && KCGI_VMINOR < 12 -#define khttp_urlpart(...) kutil_urlpart(req, __VA_ARGS__) -#endif - -#define SQL(...) #__VA_ARGS__ - -enum { DatabaseVersion = 4 }; - -static sqlite3 *db; - -static int dbParam(sqlite3_stmt *stmt, const char *param) { - int index = sqlite3_bind_parameter_index(stmt, param); - if (index) return index; - errx(EX_SOFTWARE, "no such parameter %s: %s", param, sqlite3_sql(stmt)); -} - -static void -dbBindInt(sqlite3_stmt *stmt, const char *param, sqlite3_int64 value) { - if (!sqlite3_bind_int64(stmt, dbParam(stmt, param), value)) return; - errx(EX_SOFTWARE, "sqlite3_bind_int64: %s", sqlite3_errmsg(db)); -} - -static void -dbBindText(sqlite3_stmt *stmt, const char *param, const char *value) { - if (!sqlite3_bind_text(stmt, dbParam(stmt, param), value, -1, NULL)) return; - errx(EX_SOFTWARE, "sqlite3_bind_text: %s", sqlite3_errmsg(db)); -} - -static struct { - sqlite3_stmt *networks; - sqlite3_stmt *contexts; - sqlite3_stmt *events; - sqlite3_stmt *search; -} stmt; - -static void dbClose(void) { - 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); -} - -#define ENUM_PAGES \ - X(Networks, "networks") \ - X(Contexts, "contexts") \ - X(Events, "events") \ - X(Search, "search") - -enum { -#define X(page, path) page, - ENUM_PAGES -#undef X - PagesLen, -}; - -static const char *Pages[PagesLen] = { -#define X(page, path) [page] = path, - ENUM_PAGES -#undef X -}; - -#define ENUM_KEYS \ - X(Network, "network", kvalid_stringne) \ - X(Context, "context", kvalid_stringne) \ - X(After, "after", kvalid_stringne) \ - X(Query, "query", kvalid_stringne) - -enum { -#define X(key, name, valid) key, - ENUM_KEYS -#undef X - KeysLen, -}; - -static const struct kvalid Keys[KeysLen] = { -#define X(key, name, valid) [key] = { valid, name }, - ENUM_KEYS -#undef X -}; - -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[http]) - || khttp_putc(req, '\n'); -} - -static const char *stylesheet; -static enum kcgi_err htmlStylesheet(struct khtmlreq *html) { - if (!stylesheet) return KCGI_OK; - return khtml_attr( - html, KELEM_LINK, - KATTR_REL, "stylesheet", - KATTR_HREF, stylesheet, - KATTR__MAX - ); -} - -static enum kcgi_err htmlHead(struct khtmlreq *html, const char *title) { - return 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) - || htmlStylesheet(html) - || 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) return error; - - int result; - while (SQLITE_ROW == (result = sqlite3_step(stmt.networks))) { - const char *network = (const char *)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) break; - } - if (result != SQLITE_DONE) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); - - sqlite3_reset(stmt.networks); - return error || khtml_close(&html); -} - -static const char *ContextsQuery = SQL( - SELECT name - FROM contexts - WHERE network = :network AND coalesce(query = :query, true) - ORDER BY query, name; -); - -bool public = false; - -static enum kcgi_err contexts(struct kreq *req) { - if (!req->fieldmap[Network]) return fail(req, KHTTP_404); - const char *network = req->fieldmap[Network]->parsed.s; - - 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, network) - || khtml_elem(&html, KELEM_UL); - if (error) return error; - - dbBindText(stmt.contexts, ":network", network); - if (public) dbBindInt(stmt.contexts, ":query", false); - - int result; - while (SQLITE_ROW == (result = sqlite3_step(stmt.contexts))) { - const char *context = (const char *)sqlite3_column_text(stmt.contexts, 0); - char *href = khttp_urlpart( - NULL, NULL, Pages[Events], - Keys[Network].name, network, - Keys[Context].name, context, - 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, context) - || khtml_closeelem(&html, 2); - free(href); - if (error) break; - } - if (result != SQLITE_DONE) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); - - sqlite3_reset(stmt.contexts); - return error || khtml_close(&html); -} - -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); - } - 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); - } -} - -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; - - for (int opt; 0 < (opt = getopt(argc, argv, "fps:"));) { - switch (opt) { - break; case 'f': fastCGI = true; - break; case 'p': public = true; - break; case 's': stylesheet = optarg; - break; default: return EX_USAGE; - } - } - if (optind == argc) errx(EX_USAGE, "database path required"); - - int error = sqlite3_open_v2(argv[optind], &db, SQLITE_OPEN_READONLY, NULL); - if (error) errx(EX_NOINPUT, "%s: %s", argv[optind], sqlite3_errmsg(db)); - atexit(dbClose); - - sqlite3_stmt *check; - error = sqlite3_prepare_v2( - db, SQL(PRAGMA user_version;), -1, &check, NULL - ); - if (error) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); - - error = sqlite3_step(check); - if (error != SQLITE_ROW) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db)); - - int version = sqlite3_column_int(check, 0); - if (version != DatabaseVersion) { - errx(EX_DATAERR, "unsupported database version %d", version); - } - sqlite3_finalize(check); - - prepare(&stmt.networks, NetworksQuery); - prepare(&stmt.contexts, ContextsQuery); - - if (fastCGI) { - struct kfcgi *fcgi; - enum kcgi_err error = khttp_fcgi_init( - &fcgi, Keys, KeysLen, Pages, PagesLen, Networks - ); - if (error) errx(EX_CONFIG, "khttp_fcgi_init: %s", kcgi_strerror(error)); - for ( - struct kreq req; - KCGI_OK == (error = khttp_fcgi_parse(fcgi, &req)); - khttp_free(&req) - ) { - error = request(&req); - if (error && error != KCGI_HUP) break; - } - errx(EX_PROTOCOL, "khttp_fcgi_parse: %s", kcgi_strerror(error)); - } else { - struct kreq req; - enum kcgi_err error = khttp_parse( - &req, Keys, KeysLen, Pages, PagesLen, Networks - ); - if (error) errx(EX_PROTOCOL, "khttp_parse: %s", kcgi_strerror(error)); - error = request(&req); - if (error) errx(EX_PROTOCOL, "%s", kcgi_strerror(error)); - khttp_free(&req); - } -} |