commit 043e215466c0e8e6e58831c7b045cd4cf040fd42
parent ea0081924d6bab2aae7dde1224fd98d73d71c98c
Author: Claudio Alessi <smoppy@gmail.com>
Date: Mon, 21 Mar 2016 13:12:41 +0100
Rename core.c to myadm.c (tmp).
Diffstat:
M | Makefile | | | 4 | ++-- |
M | config.mk | | | 2 | +- |
D | core.c | | | 745 | ------------------------------------------------------------------------------- |
A | myadm.c | | | 749 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
4 files changed, 752 insertions(+), 748 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,9 +1,9 @@
-# core - ???
+# myadm - ???
# See LICENSE file for copyright and license details.
include config.mk
-APPNAME=core
+APPNAME=myadm
SRC = ${APPNAME}.c
OBJ = ${SRC:.c=.o}
diff --git a/config.mk b/config.mk
@@ -1,4 +1,4 @@
-# core - ???
+# myadm - ???
VERSION = 0.1
# Customize below to fit your system
diff --git a/core.c b/core.c
@@ -1,745 +0,0 @@
-/* See LICENSE file for copyright and license details.
- *
- * An experiment which is working surprisingly well.
- *
- * Relevant docs:
- * http://svn.clifford.at/stfl/trunk/README
- * http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
-*/
-
-#include <stdio.h>
-#include <stdarg.h>
-#include <string.h>
-#include <stdlib.h>
-#include <signal.h>
-
-#include <mysql.h>
-#include <stfl.h>
-#include <langinfo.h>
-#include <locale.h>
-#include <curses.h> /* For curs_set() */
-
-#include "arg.h"
-char *argv0;
-
-#define LENGTH(X) (sizeof X / sizeof X[0])
-#define QUOTE(S) (stfl_ipool_fromwc(ipool, stfl_quote(stfl_ipool_towc(ipool, S))))
-#define LINESIZE(N) (MAXCOLSZ * (N) + fldseplen * ((N) - 1) + 1);
-
-typedef union {
- int i;
- unsigned int ui;
- float f;
- const void *v;
-} Arg;
-
-typedef struct Item Item;
-struct Item {
- char **cols;
- int *lens;
- int ncols;
- unsigned int flags;
- Item *next;
-};
-
-typedef struct Field Field;
-struct Field {
- char name[64]; /* MySQL max field name is 64 bytes */
- int len;
- Field *next;
-};
-
-typedef struct {
- const char *mode;
- const wchar_t *modkey;
- void (*func)(const Arg *);
- const Arg arg;
-} Key;
-
-typedef struct {
- char *name;
- void (*func)(const Arg *arg);
-} Mode;
-
-typedef struct View View;
-struct View {
- Mode *mode;
- Item *items;
- Item *choice;
- Field *fields;
- int nitems;
- int nfields;
- struct stfl_form *form;
- View *next;
-};
-
-/* function declarations */
-void apply(const Arg *arg);
-void attach(View *v);
-void attachfieldto(Field *f, Field **ff);
-void attachitemto(Item *i, Item **ii);
-char ask(const char *msg, char *opts);
-void cleanup(void);
-void cleanupfields(Field **fields);
-void cleanupitems(Item **items);
-void cleanupview(View *v);
-Item *cloneitem(Item *item);
-void databases(const Arg *arg);
-void detach(View *v);
-void detachfieldfrom(Field *f, Field **ff);
-void detachitemfrom(Item *i, Item **ii);
-void die(const char *errstr, ...);
-void *ecalloc(size_t nmemb, size_t size);
-void flagas(const Arg *arg);
-Item *getitem(void);
-int *getmaxlengths(View *view);
-void help(const Arg *arg);
-void itempos(const Arg *arg);
-MYSQL_RES *mysql_exec(const char *sqlstr, ...);
-int mysql_fields(MYSQL_RES *res, Field **fields);
-int mysql_items(MYSQL_RES *res, Item **items);
-void mysql_listview(MYSQL_RES *res, int showfds);
-View *newaview(const char *name, void (*func)(const Arg *arg));
-void stfl_showfields(Field *fds, int *lens);
-void stfl_showitems(Item *items, int *lens);
-void quit(const Arg *arg);
-void records(const Arg *arg);
-void reload(const Arg *arg);
-void run(void);
-void setup(void);
-void sigint_handler(int sig);
-void stfl_setf(const char *name, const char *fmtstr, ...);
-void stfl_putitem(Item *item, int *lens);
-int stripesc(char *src, char *dst, int len);
-void tables(const Arg *arg);
-void viewprev(const Arg *arg);
-
-#include "config.h"
-
-/* variables */
-static int running = 1;
-static MYSQL *mysql;
-static View *views, *selview = NULL;
-static struct stfl_ipool *ipool;
-static int fldseplen;
-
-/* function implementations */
-void
-apply(const Arg *arg) {
- /* XXX if no pending changes, notice and returns */
- if(arg->i) {
- char *opts = "yn";
- if(ask("Apply changes ([y]/n)?", opts) != opts[0])
- return;
- }
- /* XXX ... */
- stfl_setf("status", "Changes applied.");
-}
-
-void
-attach(View *v) {
- v->next = views;
- views = v;
-}
-
-void
-attachfieldto(Field *f, Field **ff) {
- Field **l;
-
- for(l = ff; *l && (*l)->next; l = &(*l)->next);
- if(!*l)
- *l = f;
- else
- (*l)->next = f;
-}
-
-void
-attachitemto(Item *i, Item **ii) {
- Item **l;
-
- for(l = ii; *l && (*l)->next; l = &(*l)->next);
- if(!*l)
- *l = i;
- else
- (*l)->next = i;
-}
-
-char
-ask(const char *msg, char *opts) {
- char *o, c;
-
- stfl_setf("status", msg);
- stfl_run(selview->form, -1);
- while((c = getchar())) {
- if(c == '\r') {
- o = &opts[0];
- break;
- }
- for(o = opts; *o; ++o)
- if(c == *o)
- break;
- if(*o)
- break;
- }
- stfl_setf("status", "");
- return *o;
-}
-
-void
-cleanup(void) {
- while(views)
- cleanupview(views);
- stfl_reset();
- stfl_ipool_destroy(ipool);
- mysql_close(mysql);
-}
-
-void
-cleanupview(View *v) {
- detach(v);
- cleanupitems(&v->items);
- cleanupfields(&v->fields);
- if(v->form)
- stfl_free(v->form);
- if(v->choice)
- cleanupitems(&v->choice);
- free(v);
-}
-
-void
-cleanupfields(Field **fields) {
- Field *f;
-
- while(*fields) {
- f = *fields;
- detachfieldfrom(f, fields);
- free(f);
- }
-}
-
-void
-cleanupitems(Item **items) {
- Item *i;
-
- while(*items) {
- i = *items;
- detachitemfrom(i, items);
- while(--i->ncols >= 0)
- free(i->cols[i->ncols]);
- free(i->lens);
- free(i->cols);
- free(i);
- }
-}
-
-Item *
-cloneitem(Item *item) {
- Item *ic;
- int i, nfds;
-
- if(!item)
- return NULL;
-
- nfds = item->ncols;
- ic = ecalloc(1, sizeof(Item));
- ic->cols = ecalloc(nfds, sizeof(char *));
- ic->lens = ecalloc(nfds, sizeof(int));
- ic->ncols = nfds;
- ic->flags = item->flags;
- for(i = 0; i < nfds; ++i) {
- ic->cols[i] = ecalloc(item->lens[i], sizeof(char));
- ic->lens[i] = item->lens[i];
- memcpy(ic->cols[i], item->cols[i], item->lens[i]);
- }
- return ic;
-}
-
-void
-databases(const Arg *arg) {
- int refresh = (selview && !strcmp(selview->mode->name, "databases"));
- MYSQL_RES *res;
-
- if(!(res = mysql_exec("show databases")))
- die("databases\n");
- if(!refresh)
- selview = newaview("databases", databases);
- mysql_listview(res, 0);
- mysql_free_result(res);
- stfl_setf("title", "Databases in `%s`", dbhost);
- stfl_setf("info", "%d DB(s)", selview->nitems);
-}
-
-void
-detach(View *v) {
- View **tv;
-
- for(tv = &views; *tv && *tv != v; tv = &(*tv)->next);
- *tv = v->next;
-}
-
-void
-detachfieldfrom(Field *f, Field **ff) {
- Field **tf;
-
- for(tf = &(*ff); *tf && *tf != f; tf = &(*tf)->next);
- *tf = f->next;
-}
-
-void
-detachitemfrom(Item *i, Item **ii) {
- Item **ti;
-
- for(ti = &(*ii); *ti && *ti != i; ti = &(*ti)->next);
- *ti = i->next;
-}
-
-void
-die(const char *errstr, ...) {
- va_list ap;
-
- va_start(ap, errstr);
- vfprintf(stderr, errstr, ap);
- va_end(ap);
- exit(1);
-}
-
-void *
-ecalloc(size_t nmemb, size_t size) {
- void *p;
-
- if (!(p = calloc(nmemb, size)))
- die("Cannot allocate memory.");
- return p;
-}
-
-void
-flagas(const Arg *arg) {
-}
-
-Item *
-getitem(void) {
- const char *spos = stfl_ipool_fromwc(ipool, stfl_get(selview->form, L"pos"));
- Item *item;
- int pos, n;
-
- if(!(selview && spos))
- return NULL;
- pos = atoi(spos);
-
- for(item = selview->items, n = 0; item; item = item->next, ++n)
- if(n == pos)
- break;
- return item;
-}
-
-int *
-getmaxlengths(View *view) {
- Item *item;
- Field *fld;
- int i, nfds, *lens;
-
- if(view->nfields)
- nfds = view->nfields;
- else if(view->items)
- nfds = view->items->ncols;
- else
- return NULL;
-
- lens = ecalloc(nfds, sizeof(int));
- for(fld = view->fields, i = 0; fld; fld = fld->next, ++i)
- lens[i] = (fld->len <= MAXCOLSZ ? fld->len : MAXCOLSZ);
- for(item = view->items; item; item = item->next)
- for(i = 0; i < item->ncols; ++i)
- if(lens[i] < item->lens[i])
- lens[i] = (item->lens[i] <= MAXCOLSZ ? item->lens[i] : MAXCOLSZ);
- return lens;
-}
-
-void
-help(const Arg *arg) {
- int refresh = (selview && !strcmp(selview->mode->name, "help"));
-
- if(!refresh)
- selview = newaview("help", help);
- else
- stfl_free(selview->form);
- selview->form = stfl_create(L"<help.stfl>");
-}
-
-void
-itempos(const Arg *arg) {
- const char *spos = stfl_ipool_fromwc(ipool, stfl_get(selview->form, L"pos"));
- char tmp[8];
- int pos;
-
- if(!spos)
- return;
-
- pos = atoi(spos) + arg->i;
- if(pos < 0)
- pos = 0;
- else if(pos >= selview->nitems)
- pos = selview->nitems - 1;
-
- snprintf(tmp, sizeof tmp, "%d", pos);
- stfl_set(selview->form, L"pos", stfl_ipool_towc(ipool, tmp));
-}
-
-MYSQL_RES *
-mysql_exec(const char *sqlstr, ...) {
- MYSQL_RES *res;
- va_list ap;
- char sql[128];
- int sqlen;
-
- va_start(ap, sqlstr);
- sqlen = vsnprintf(sql, sizeof sql, sqlstr, ap);
- va_end(ap);
-
- if(mysql_real_query(mysql, sql, sqlen))
- return NULL;
- res = mysql_store_result(mysql);
- if(!res)
- return NULL;
- return res;
-}
-
-int
-mysql_fields(MYSQL_RES *res, Field **fields) {
- MYSQL_FIELD *fds;
- Field *field;
- int nfds, i;
-
- nfds = mysql_num_fields(res);
- if(!nfds)
- return 0;
- fds = mysql_fetch_fields(res);
- for(i = 0; i < nfds; ++i) {
- field = ecalloc(1, sizeof(Field));
- field->len = fds[i].name_length;
- memcpy(field->name, fds[i].name, field->len);
- attachfieldto(field, fields);
- }
- return nfds;
-}
-
-int
-mysql_items(MYSQL_RES *res, Item **items) {
- MYSQL_ROW row;
- Item *item;
- int i, nfds, nrows;
- unsigned long *lens;
-
- nfds = mysql_num_fields(res);
- nrows = mysql_num_rows(res);
-
- *items = NULL;
- while((row = mysql_fetch_row(res))) {
- item = ecalloc(1, sizeof(Item));
- item->lens = ecalloc(nfds, sizeof(int));
- item->cols = ecalloc(nfds, sizeof(char *));
- lens = mysql_fetch_lengths(res);
- item->ncols = nfds;
- for(i = 0; i < nfds; ++i) {
- item->cols[i] = ecalloc(lens[i], sizeof(char));
- memcpy(item->cols[i], row[i], lens[i]);
- item->lens[i] = lens[i];
- }
- attachitemto(item, items);
- }
- return nrows;
-}
-
-void
-mysql_listview(MYSQL_RES *res, int showfds) {
- int *lens;
-
- cleanupitems(&selview->items);
- selview->nitems = mysql_items(res, &selview->items);
-
- if(!selview->form) {
- selview->form = stfl_create(L"<items.stfl>");
- stfl_run(selview->form, -1); /* refresh ncurses */
- curs_set(0);
- }
-
- if(showfds) {
- cleanupfields(&selview->fields);
- selview->nfields = mysql_fields(res, &selview->fields);
- lens = getmaxlengths(selview);
- stfl_showfields(selview->fields, lens);
- }
- else {
- lens = getmaxlengths(selview);
- }
-
- stfl_showitems(selview->items, lens);
- free(lens);
-}
-
-View *
-newaview(const char *name, void (*func)(const Arg *arg)) {
- View *v;
-
- v = ecalloc(1, sizeof(View));
- v->mode = ecalloc(1, sizeof(Mode));
- v->mode->name = ecalloc(strlen(name)+1, sizeof(char));
- strcpy(v->mode->name, name);
- v->mode->func = func;
- attach(v);
- return v;
-}
-
-void
-stfl_showfields(Field *fds, int *lens) {
- Field *fld;
- char line[COLS+1], txt[MAXCOLSZ+1], col[MAXCOLSZ+1];
- int i, len, nfields, linesz;
-
- if(!(fds && lens))
- return;
-
- for(fld = fds, nfields = 0; fld; fld = fld->next, ++nfields);
-
- linesz = sizeof line;
- line[0] = '\0';
- for(fld = fds, i = 0; fld; fld = fld->next, ++i) {
- if(i) {
- strncat(line, FLDSEP, linesz);
- linesz -= fldseplen;
- if(linesz <= 0)
- break;
- }
- len = stripesc(col, fld->name, lens[i]);
- col[len] = '\0';
- snprintf(txt, sizeof txt, "%-*.*s", lens[i], lens[i], col);
- strncat(line, txt, linesz);
- linesz -= lens[i];
- if(linesz <= 0)
- break;
- }
-
- stfl_setf("subtle", "%s", line);
- stfl_setf("showsubtle", (line[0] ? "1" : "0"));
-}
-
-void
-stfl_showitems(Item *items, int *lens) {
- Item *item;
-
- stfl_modify(selview->form, L"items", L"replace_inner", L"vbox"); /* clear */
- for(item = selview->items; item; item = item->next)
- stfl_putitem(item, lens);
- stfl_set(selview->form, L"pos", L"0");
-}
-
-/* XXX Improved logic:
- * -1 only ask if there are pending changes
- * 1 always ask
- * 0 never ask */
-void
-quit(const Arg *arg) {
- if(arg->i) {
- char *opts = "yn";
- if(ask("Do you want to quit ([y]/n)?", opts) != opts[0])
- return;
- }
- running = 0;
-}
-
-void
-records(const Arg *arg) {
- int refresh = (selview && !strcmp(selview->mode->name, "records"));
- Item *choice = (refresh ? selview->choice : cloneitem(getitem()));
- MYSQL_RES *res;
- char *tbl;
-
- choice = (refresh ? selview->choice : cloneitem(getitem()));
- if(!choice->ncols)
- die("records: no choice.\n");
- tbl = calloc(choice->lens[0] + 1, sizeof(char));
- memcpy(tbl, choice->cols[0], choice->lens[0]);
- if(!(res = mysql_exec("select * from `%s`", tbl)))
- die("records: cannot select `%s`\n", tbl);
- if(!refresh) {
- selview = newaview("records", records);
- selview->choice = choice;
- }
- mysql_listview(res, 1);
- mysql_free_result(res);
- stfl_setf("title", "Records in `%s`", tbl);
- stfl_setf("info", "---Core: %d record(s)", selview->nitems);
- free(tbl);
-}
-
-void
-reload(const Arg *arg) {
- const wchar_t *pos = stfl_get(selview->form, L"pos");
- if(!selview->mode->func)
- return;
- selview->mode->func(NULL);
- if(pos)
- stfl_set(selview->form, L"pos", pos);
-}
-
-void
-run(void) {
- Key *k;
- const wchar_t *ev;
- unsigned int i;
-
- while(running) {
- if(!(ev = stfl_run(selview->form, 0)))
- continue;
- stfl_setf("status", "");
- k = NULL;
- for(i = 0; i < LENGTH(keys); ++i)
- if(!((keys[i].mode && strcmp(selview->mode->name, keys[i].mode))
- || wcscmp(ev, keys[i].modkey)))
- k = &keys[i];
- if(k)
- k->func(&k->arg);
- }
-}
-
-void
-setup(void) {
- struct sigaction sa;
-
- mysql = mysql_init(NULL);
- if(mysql_real_connect(mysql, dbhost, dbuser, dbpass, NULL, 0, NULL, 0) == NULL)
- die("Cannot connect to the database.\n");
-
- fldseplen = strlen(FLDSEP);
- ipool = stfl_ipool_create(nl_langinfo(CODESET));
- welcome(NULL);
- stfl_setf("status", "Welcome to %s-%s", __FILE__, VERSION);
-
- sa.sa_flags = 0;
- sigemptyset(&sa.sa_mask);
- sa.sa_handler = sigint_handler;
- sigaction(SIGINT, &sa, NULL);
-}
-
-void
-sigint_handler(int sig) {
- Arg arg = {.i = 1};
- quit(&arg);
-}
-
-void
-stfl_setf(const char *name, const char *fmtstr, ...) {
- va_list ap;
- char s[256];
-
- va_start(ap, fmtstr);
- vsnprintf(s, sizeof s, fmtstr, ap);
- va_end(ap);
- stfl_set(selview->form, stfl_ipool_towc(ipool, name), stfl_ipool_towc(ipool, s));
-}
-
-void
-stfl_putitem(Item *item, int *lens) {
- const char *qline;
- char *stfl, line[COLS + 1], txt[MAXCOLSZ+1], col[MAXCOLSZ+1];
- int i, len, linesz;
-
- if(!(item && lens))
- return;
-
- linesz = sizeof line;
- line[0] = '\0';
- for(i = 0; i < item->ncols; ++i) {
- if(i) {
- strncat(line, FLDSEP, linesz);
- linesz -= fldseplen;
- if(linesz <= 0)
- break;
- }
- len = stripesc(col, item->cols[i], lens[i]);
- col[len] = '\0';
- snprintf(txt, sizeof txt, "%-*.*s", lens[i], lens[i], col);
- strncat(line, txt, linesz);
- linesz -= lens[i];
- if(linesz <= 0)
- break;
- }
-
- /* XXX cleanup */
- qline = QUOTE(line);
- i = strlen("listitem text:");
- len = strlen(qline) + i;
- stfl = ecalloc(len + 1, sizeof(char));
- memcpy(stfl, "listitem text:", i);
- memcpy(&stfl[i], qline, len - i - 1);
- stfl_modify(selview->form, L"items", L"append", stfl_ipool_towc(ipool, stfl));
-
- free(stfl);
-}
-
-int
-stripesc(char *dst, char *src, int len) {
- int i, n;
-
- for(i = 0, n = 0; i < len; ++i)
- if(src[i] != '\r' && src[i] != '\n' && src[i] != '\t')
- dst[n++] = src[i];
- return n;
-}
-
-void
-usage(void) {
- die("Usage: %s [-vhup <arg>]\n", argv0);
-}
-
-void
-tables(const Arg *arg) {
- int refresh = (selview && !strcmp(selview->mode->name, "tables"));
- Item *choice = (refresh ? selview->choice : cloneitem(getitem()));
- MYSQL_RES *res;
-
- if(!refresh) {
- selview = newaview("tables", tables);
- selview->choice = choice;
- mysql_select_db(mysql, choice->cols[0]);
- }
- if(!(res = mysql_exec("show tables")))
- die("tables\n");
- mysql_listview(res, 0);
- mysql_free_result(res);
- stfl_setf("title", "Tables in `%s`", choice->cols[0]);
- stfl_setf("info", "%d table(s)", selview->nitems);
-}
-
-void
-viewprev(const Arg *arg) {
- View *v;
-
- if(!selview->next)
- return;
- v = selview->next;
- cleanupview(selview);
- selview = v;
-}
-
-int
-main(int argc, char **argv) {
- ARGBEGIN {
- case 'h':
- dbhost = EARGF(usage());
- break;
- case 'u':
- dbuser = EARGF(usage());
- break;
- case 'p':
- dbpass = EARGF(usage());
- break;
- case 'v':
- die("%s " VERSION " (c) 2016 Claudio Alessi\n", argv0);
- } ARGEND;
-
- setup();
- run();
- cleanup();
- return 0;
-}
diff --git a/myadm.c b/myadm.c
@@ -0,0 +1,749 @@
+/* See LICENSE file for copyright and license details.
+ *
+ * myadm is a text-based TUI for MySQL. It emulates the mutt interface through
+ * the STFL library and talk with the SQL server using libmysqlclient.
+ *
+ * Each piece of information displayed is called an item. Items are organized
+ * in a linked items list on each view. A view contains an STFL form where all
+ * graphical elements are drawn along with all related informations. Each item
+ * contains a bit array to indicate tags of an item.
+ *
+ * To understand everything else, start reading main().
+*/
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+
+#include <mysql.h>
+#include <stfl.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <curses.h> /* For curs_set() */
+
+#include "arg.h"
+char *argv0;
+
+#define LENGTH(X) (sizeof X / sizeof X[0])
+#define QUOTE(S) (stfl_ipool_fromwc(ipool, stfl_quote(stfl_ipool_towc(ipool, S))))
+#define LINESIZE(N) (MAXCOLSZ * (N) + fldseplen * ((N) - 1) + 1);
+
+typedef union {
+ int i;
+ unsigned int ui;
+ float f;
+ const void *v;
+} Arg;
+
+typedef struct Item Item;
+struct Item {
+ char **cols;
+ int *lens;
+ int ncols;
+ unsigned int flags;
+ Item *next;
+};
+
+typedef struct Field Field;
+struct Field {
+ char name[64]; /* MySQL max field name is 64 bytes */
+ int len;
+ Field *next;
+};
+
+typedef struct {
+ const char *mode;
+ const wchar_t *modkey;
+ void (*func)(const Arg *);
+ const Arg arg;
+} Key;
+
+typedef struct {
+ char *name;
+ void (*func)(const Arg *arg);
+} Mode;
+
+typedef struct View View;
+struct View {
+ Mode *mode;
+ Item *items;
+ Item *choice;
+ Field *fields;
+ int nitems;
+ int nfields;
+ struct stfl_form *form;
+ View *next;
+};
+
+/* function declarations */
+void apply(const Arg *arg);
+void attach(View *v);
+void attachfieldto(Field *f, Field **ff);
+void attachitemto(Item *i, Item **ii);
+char ask(const char *msg, char *opts);
+void cleanup(void);
+void cleanupfields(Field **fields);
+void cleanupitems(Item **items);
+void cleanupview(View *v);
+Item *cloneitem(Item *item);
+void databases(const Arg *arg);
+void detach(View *v);
+void detachfieldfrom(Field *f, Field **ff);
+void detachitemfrom(Item *i, Item **ii);
+void die(const char *errstr, ...);
+void *ecalloc(size_t nmemb, size_t size);
+void flagas(const Arg *arg);
+Item *getitem(void);
+int *getmaxlengths(View *view);
+void help(const Arg *arg);
+void itempos(const Arg *arg);
+MYSQL_RES *mysql_exec(const char *sqlstr, ...);
+int mysql_fields(MYSQL_RES *res, Field **fields);
+int mysql_items(MYSQL_RES *res, Item **items);
+void mysql_listview(MYSQL_RES *res, int showfds);
+View *newaview(const char *name, void (*func)(const Arg *arg));
+void stfl_showfields(Field *fds, int *lens);
+void stfl_showitems(Item *items, int *lens);
+void quit(const Arg *arg);
+void records(const Arg *arg);
+void reload(const Arg *arg);
+void run(void);
+void setup(void);
+void sigint_handler(int sig);
+void stfl_setf(const char *name, const char *fmtstr, ...);
+void stfl_putitem(Item *item, int *lens);
+int stripesc(char *src, char *dst, int len);
+void tables(const Arg *arg);
+void viewprev(const Arg *arg);
+
+#include "config.h"
+
+/* variables */
+static int running = 1;
+static MYSQL *mysql;
+static View *views, *selview = NULL;
+static struct stfl_ipool *ipool;
+static int fldseplen;
+
+/* function implementations */
+void
+apply(const Arg *arg) {
+ /* XXX if no pending changes, notice and returns */
+ if(arg->i) {
+ char *opts = "yn";
+ if(ask("Apply changes ([y]/n)?", opts) != opts[0])
+ return;
+ }
+ /* XXX ... */
+ stfl_setf("status", "Changes applied.");
+}
+
+void
+attach(View *v) {
+ v->next = views;
+ views = v;
+}
+
+void
+attachfieldto(Field *f, Field **ff) {
+ Field **l;
+
+ for(l = ff; *l && (*l)->next; l = &(*l)->next);
+ if(!*l)
+ *l = f;
+ else
+ (*l)->next = f;
+}
+
+void
+attachitemto(Item *i, Item **ii) {
+ Item **l;
+
+ for(l = ii; *l && (*l)->next; l = &(*l)->next);
+ if(!*l)
+ *l = i;
+ else
+ (*l)->next = i;
+}
+
+char
+ask(const char *msg, char *opts) {
+ char *o, c;
+
+ stfl_setf("status", msg);
+ stfl_run(selview->form, -1);
+ while((c = getchar())) {
+ if(c == '\r') {
+ o = &opts[0];
+ break;
+ }
+ for(o = opts; *o; ++o)
+ if(c == *o)
+ break;
+ if(*o)
+ break;
+ }
+ stfl_setf("status", "");
+ return *o;
+}
+
+void
+cleanup(void) {
+ while(views)
+ cleanupview(views);
+ stfl_reset();
+ stfl_ipool_destroy(ipool);
+ mysql_close(mysql);
+}
+
+void
+cleanupview(View *v) {
+ detach(v);
+ cleanupitems(&v->items);
+ cleanupfields(&v->fields);
+ if(v->form)
+ stfl_free(v->form);
+ if(v->choice)
+ cleanupitems(&v->choice);
+ free(v);
+}
+
+void
+cleanupfields(Field **fields) {
+ Field *f;
+
+ while(*fields) {
+ f = *fields;
+ detachfieldfrom(f, fields);
+ free(f);
+ }
+}
+
+void
+cleanupitems(Item **items) {
+ Item *i;
+
+ while(*items) {
+ i = *items;
+ detachitemfrom(i, items);
+ while(--i->ncols >= 0)
+ free(i->cols[i->ncols]);
+ free(i->lens);
+ free(i->cols);
+ free(i);
+ }
+}
+
+Item *
+cloneitem(Item *item) {
+ Item *ic;
+ int i, nfds;
+
+ if(!item)
+ return NULL;
+
+ nfds = item->ncols;
+ ic = ecalloc(1, sizeof(Item));
+ ic->cols = ecalloc(nfds, sizeof(char *));
+ ic->lens = ecalloc(nfds, sizeof(int));
+ ic->ncols = nfds;
+ ic->flags = item->flags;
+ for(i = 0; i < nfds; ++i) {
+ ic->cols[i] = ecalloc(item->lens[i], sizeof(char));
+ ic->lens[i] = item->lens[i];
+ memcpy(ic->cols[i], item->cols[i], item->lens[i]);
+ }
+ return ic;
+}
+
+void
+databases(const Arg *arg) {
+ int refresh = (selview && !strcmp(selview->mode->name, "databases"));
+ MYSQL_RES *res;
+
+ if(!(res = mysql_exec("show databases")))
+ die("databases\n");
+ if(!refresh)
+ selview = newaview("databases", databases);
+ mysql_listview(res, 0);
+ mysql_free_result(res);
+ stfl_setf("title", "Databases in `%s`", dbhost);
+ stfl_setf("info", "%d DB(s)", selview->nitems);
+}
+
+void
+detach(View *v) {
+ View **tv;
+
+ for(tv = &views; *tv && *tv != v; tv = &(*tv)->next);
+ *tv = v->next;
+}
+
+void
+detachfieldfrom(Field *f, Field **ff) {
+ Field **tf;
+
+ for(tf = &(*ff); *tf && *tf != f; tf = &(*tf)->next);
+ *tf = f->next;
+}
+
+void
+detachitemfrom(Item *i, Item **ii) {
+ Item **ti;
+
+ for(ti = &(*ii); *ti && *ti != i; ti = &(*ti)->next);
+ *ti = i->next;
+}
+
+void
+die(const char *errstr, ...) {
+ va_list ap;
+
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+ exit(1);
+}
+
+void *
+ecalloc(size_t nmemb, size_t size) {
+ void *p;
+
+ if (!(p = calloc(nmemb, size)))
+ die("Cannot allocate memory.");
+ return p;
+}
+
+void
+flagas(const Arg *arg) {
+}
+
+Item *
+getitem(void) {
+ const char *spos = stfl_ipool_fromwc(ipool, stfl_get(selview->form, L"pos"));
+ Item *item;
+ int pos, n;
+
+ if(!(selview && spos))
+ return NULL;
+ pos = atoi(spos);
+
+ for(item = selview->items, n = 0; item; item = item->next, ++n)
+ if(n == pos)
+ break;
+ return item;
+}
+
+int *
+getmaxlengths(View *view) {
+ Item *item;
+ Field *fld;
+ int i, nfds, *lens;
+
+ if(view->nfields)
+ nfds = view->nfields;
+ else if(view->items)
+ nfds = view->items->ncols;
+ else
+ return NULL;
+
+ lens = ecalloc(nfds, sizeof(int));
+ for(fld = view->fields, i = 0; fld; fld = fld->next, ++i)
+ lens[i] = (fld->len <= MAXCOLSZ ? fld->len : MAXCOLSZ);
+ for(item = view->items; item; item = item->next)
+ for(i = 0; i < item->ncols; ++i)
+ if(lens[i] < item->lens[i])
+ lens[i] = (item->lens[i] <= MAXCOLSZ ? item->lens[i] : MAXCOLSZ);
+ return lens;
+}
+
+void
+help(const Arg *arg) {
+ int refresh = (selview && !strcmp(selview->mode->name, "help"));
+
+ if(!refresh)
+ selview = newaview("help", help);
+ else
+ stfl_free(selview->form);
+ selview->form = stfl_create(L"<help.stfl>");
+}
+
+void
+itempos(const Arg *arg) {
+ const char *spos = stfl_ipool_fromwc(ipool, stfl_get(selview->form, L"pos"));
+ char tmp[8];
+ int pos;
+
+ if(!spos)
+ return;
+
+ pos = atoi(spos) + arg->i;
+ if(pos < 0)
+ pos = 0;
+ else if(pos >= selview->nitems)
+ pos = selview->nitems - 1;
+
+ snprintf(tmp, sizeof tmp, "%d", pos);
+ stfl_set(selview->form, L"pos", stfl_ipool_towc(ipool, tmp));
+}
+
+MYSQL_RES *
+mysql_exec(const char *sqlstr, ...) {
+ MYSQL_RES *res;
+ va_list ap;
+ char sql[128];
+ int sqlen;
+
+ va_start(ap, sqlstr);
+ sqlen = vsnprintf(sql, sizeof sql, sqlstr, ap);
+ va_end(ap);
+
+ if(mysql_real_query(mysql, sql, sqlen))
+ return NULL;
+ res = mysql_store_result(mysql);
+ if(!res)
+ return NULL;
+ return res;
+}
+
+int
+mysql_fields(MYSQL_RES *res, Field **fields) {
+ MYSQL_FIELD *fds;
+ Field *field;
+ int nfds, i;
+
+ nfds = mysql_num_fields(res);
+ if(!nfds)
+ return 0;
+ fds = mysql_fetch_fields(res);
+ for(i = 0; i < nfds; ++i) {
+ field = ecalloc(1, sizeof(Field));
+ field->len = fds[i].name_length;
+ memcpy(field->name, fds[i].name, field->len);
+ attachfieldto(field, fields);
+ }
+ return nfds;
+}
+
+int
+mysql_items(MYSQL_RES *res, Item **items) {
+ MYSQL_ROW row;
+ Item *item;
+ int i, nfds, nrows;
+ unsigned long *lens;
+
+ nfds = mysql_num_fields(res);
+ nrows = mysql_num_rows(res);
+
+ *items = NULL;
+ while((row = mysql_fetch_row(res))) {
+ item = ecalloc(1, sizeof(Item));
+ item->lens = ecalloc(nfds, sizeof(int));
+ item->cols = ecalloc(nfds, sizeof(char *));
+ lens = mysql_fetch_lengths(res);
+ item->ncols = nfds;
+ for(i = 0; i < nfds; ++i) {
+ item->cols[i] = ecalloc(lens[i], sizeof(char));
+ memcpy(item->cols[i], row[i], lens[i]);
+ item->lens[i] = lens[i];
+ }
+ attachitemto(item, items);
+ }
+ return nrows;
+}
+
+void
+mysql_listview(MYSQL_RES *res, int showfds) {
+ int *lens;
+
+ cleanupitems(&selview->items);
+ selview->nitems = mysql_items(res, &selview->items);
+
+ if(!selview->form) {
+ selview->form = stfl_create(L"<items.stfl>");
+ stfl_run(selview->form, -1); /* refresh ncurses */
+ curs_set(0);
+ }
+
+ if(showfds) {
+ cleanupfields(&selview->fields);
+ selview->nfields = mysql_fields(res, &selview->fields);
+ lens = getmaxlengths(selview);
+ stfl_showfields(selview->fields, lens);
+ }
+ else {
+ lens = getmaxlengths(selview);
+ }
+
+ stfl_showitems(selview->items, lens);
+ free(lens);
+}
+
+View *
+newaview(const char *name, void (*func)(const Arg *arg)) {
+ View *v;
+
+ v = ecalloc(1, sizeof(View));
+ v->mode = ecalloc(1, sizeof(Mode));
+ v->mode->name = ecalloc(strlen(name)+1, sizeof(char));
+ strcpy(v->mode->name, name);
+ v->mode->func = func;
+ attach(v);
+ return v;
+}
+
+void
+stfl_showfields(Field *fds, int *lens) {
+ Field *fld;
+ char line[COLS+1], txt[MAXCOLSZ+1], col[MAXCOLSZ+1];
+ int i, len, nfields, linesz;
+
+ if(!(fds && lens))
+ return;
+
+ for(fld = fds, nfields = 0; fld; fld = fld->next, ++nfields);
+
+ linesz = sizeof line;
+ line[0] = '\0';
+ for(fld = fds, i = 0; fld; fld = fld->next, ++i) {
+ if(i) {
+ strncat(line, FLDSEP, linesz);
+ linesz -= fldseplen;
+ if(linesz <= 0)
+ break;
+ }
+ len = stripesc(col, fld->name, lens[i]);
+ col[len] = '\0';
+ snprintf(txt, sizeof txt, "%-*.*s", lens[i], lens[i], col);
+ strncat(line, txt, linesz);
+ linesz -= lens[i];
+ if(linesz <= 0)
+ break;
+ }
+
+ stfl_setf("subtle", "%s", line);
+ stfl_setf("showsubtle", (line[0] ? "1" : "0"));
+}
+
+void
+stfl_showitems(Item *items, int *lens) {
+ Item *item;
+
+ stfl_modify(selview->form, L"items", L"replace_inner", L"vbox"); /* clear */
+ for(item = selview->items; item; item = item->next)
+ stfl_putitem(item, lens);
+ stfl_set(selview->form, L"pos", L"0");
+}
+
+/* XXX Improved logic:
+ * -1 only ask if there are pending changes
+ * 1 always ask
+ * 0 never ask */
+void
+quit(const Arg *arg) {
+ if(arg->i) {
+ char *opts = "yn";
+ if(ask("Do you want to quit ([y]/n)?", opts) != opts[0])
+ return;
+ }
+ running = 0;
+}
+
+void
+records(const Arg *arg) {
+ int refresh = (selview && !strcmp(selview->mode->name, "records"));
+ Item *choice = (refresh ? selview->choice : cloneitem(getitem()));
+ MYSQL_RES *res;
+ char *tbl;
+
+ choice = (refresh ? selview->choice : cloneitem(getitem()));
+ if(!choice->ncols)
+ die("records: no choice.\n");
+ tbl = calloc(choice->lens[0] + 1, sizeof(char));
+ memcpy(tbl, choice->cols[0], choice->lens[0]);
+ if(!(res = mysql_exec("select * from `%s`", tbl)))
+ die("records: cannot select `%s`\n", tbl);
+ if(!refresh) {
+ selview = newaview("records", records);
+ selview->choice = choice;
+ }
+ mysql_listview(res, 1);
+ mysql_free_result(res);
+ stfl_setf("title", "Records in `%s`", tbl);
+ stfl_setf("info", "---Core: %d record(s)", selview->nitems);
+ free(tbl);
+}
+
+void
+reload(const Arg *arg) {
+ const wchar_t *pos = stfl_get(selview->form, L"pos");
+ if(!selview->mode->func)
+ return;
+ selview->mode->func(NULL);
+ if(pos)
+ stfl_set(selview->form, L"pos", pos);
+}
+
+void
+run(void) {
+ Key *k;
+ const wchar_t *ev;
+ unsigned int i;
+
+ while(running) {
+ if(!(ev = stfl_run(selview->form, 0)))
+ continue;
+ stfl_setf("status", "");
+ k = NULL;
+ for(i = 0; i < LENGTH(keys); ++i)
+ if(!((keys[i].mode && strcmp(selview->mode->name, keys[i].mode))
+ || wcscmp(ev, keys[i].modkey)))
+ k = &keys[i];
+ if(k)
+ k->func(&k->arg);
+ }
+}
+
+void
+setup(void) {
+ struct sigaction sa;
+
+ mysql = mysql_init(NULL);
+ if(mysql_real_connect(mysql, dbhost, dbuser, dbpass, NULL, 0, NULL, 0) == NULL)
+ die("Cannot connect to the database.\n");
+
+ fldseplen = strlen(FLDSEP);
+ ipool = stfl_ipool_create(nl_langinfo(CODESET));
+ welcome(NULL);
+ stfl_setf("status", "Welcome to %s-%s", __FILE__, VERSION);
+
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = sigint_handler;
+ sigaction(SIGINT, &sa, NULL);
+}
+
+void
+sigint_handler(int sig) {
+ Arg arg = {.i = 1};
+ quit(&arg);
+}
+
+void
+stfl_setf(const char *name, const char *fmtstr, ...) {
+ va_list ap;
+ char s[256];
+
+ va_start(ap, fmtstr);
+ vsnprintf(s, sizeof s, fmtstr, ap);
+ va_end(ap);
+ stfl_set(selview->form, stfl_ipool_towc(ipool, name), stfl_ipool_towc(ipool, s));
+}
+
+void
+stfl_putitem(Item *item, int *lens) {
+ const char *qline;
+ char *stfl, line[COLS + 1], txt[MAXCOLSZ+1], col[MAXCOLSZ+1];
+ int i, len, linesz;
+
+ if(!(item && lens))
+ return;
+
+ linesz = sizeof line;
+ line[0] = '\0';
+ for(i = 0; i < item->ncols; ++i) {
+ if(i) {
+ strncat(line, FLDSEP, linesz);
+ linesz -= fldseplen;
+ if(linesz <= 0)
+ break;
+ }
+ len = stripesc(col, item->cols[i], lens[i]);
+ col[len] = '\0';
+ snprintf(txt, sizeof txt, "%-*.*s", lens[i], lens[i], col);
+ strncat(line, txt, linesz);
+ linesz -= lens[i];
+ if(linesz <= 0)
+ break;
+ }
+
+ /* XXX cleanup */
+ qline = QUOTE(line);
+ i = strlen("listitem text:");
+ len = strlen(qline) + i;
+ stfl = ecalloc(len + 1, sizeof(char));
+ memcpy(stfl, "listitem text:", i);
+ memcpy(&stfl[i], qline, len - i - 1);
+ stfl_modify(selview->form, L"items", L"append", stfl_ipool_towc(ipool, stfl));
+
+ free(stfl);
+}
+
+int
+stripesc(char *dst, char *src, int len) {
+ int i, n;
+
+ for(i = 0, n = 0; i < len; ++i)
+ if(src[i] != '\r' && src[i] != '\n' && src[i] != '\t')
+ dst[n++] = src[i];
+ return n;
+}
+
+void
+usage(void) {
+ die("Usage: %s [-vhup <arg>]\n", argv0);
+}
+
+void
+tables(const Arg *arg) {
+ int refresh = (selview && !strcmp(selview->mode->name, "tables"));
+ Item *choice = (refresh ? selview->choice : cloneitem(getitem()));
+ MYSQL_RES *res;
+
+ if(!refresh) {
+ selview = newaview("tables", tables);
+ selview->choice = choice;
+ mysql_select_db(mysql, choice->cols[0]);
+ }
+ if(!(res = mysql_exec("show tables")))
+ die("tables\n");
+ mysql_listview(res, 0);
+ mysql_free_result(res);
+ stfl_setf("title", "Tables in `%s`", choice->cols[0]);
+ stfl_setf("info", "%d table(s)", selview->nitems);
+}
+
+void
+viewprev(const Arg *arg) {
+ View *v;
+
+ if(!selview->next)
+ return;
+ v = selview->next;
+ cleanupview(selview);
+ selview = v;
+}
+
+int
+main(int argc, char **argv) {
+ ARGBEGIN {
+ case 'h':
+ dbhost = EARGF(usage());
+ break;
+ case 'u':
+ dbuser = EARGF(usage());
+ break;
+ case 'p':
+ dbpass = EARGF(usage());
+ break;
+ case 'v':
+ die("%s " VERSION " (c) 2016 Claudio Alessi\n", argv0);
+ } ARGEND;
+
+ setup();
+ run();
+ cleanup();
+ return 0;
+}