myadm

Simple MySQL client for the terminal
git clone git://git.bitsmanent.org/myadm
Log | Files | Refs | README | LICENSE

myadm.c (19188B)


      1 /* See LICENSE file for copyright and license details.
      2  *
      3  * myadm is a text-based TUI for MySQL. It emulates the mutt interface through
      4  * the STFL library and talk with the SQL server using libmysqlclient.
      5  *
      6  * Each piece of information displayed is called an item. Items are organized
      7  * in a linked items list on each view. A view contains an STFL form where all
      8  * graphical elements are drawn along with all related informations. Each item
      9  * contains a bit array to indicate tags of an item.
     10  *
     11  * To understand everything else, start reading main().
     12 */
     13 
     14 #include <stdio.h>
     15 #include <stdarg.h>
     16 #include <string.h>
     17 #include <stdlib.h>
     18 #include <signal.h>
     19 #include <ctype.h>
     20 #include <mysql.h>
     21 #include <stfl.h>
     22 #include <langinfo.h>
     23 #include <locale.h>
     24 #include <curses.h>
     25 
     26 #include <unistd.h>
     27 #include <sys/types.h>
     28 #include <sys/wait.h>
     29 #include <sys/stat.h>
     30 #include <fcntl.h>
     31 
     32 /* STFL fragments (generated via stflfrag) */
     33 #include "fragments.h"
     34 
     35 #include "arg.h"
     36 char *argv0;
     37 
     38 #define QUOTE(S)		(stfl_ipool_fromwc(ipool, stfl_quote(stfl_ipool_towc(ipool, S))))
     39 #define ISCURVIEW(N)		!(N && selview && strcmp(selview->name, N))
     40 #define LENGTH(X)		(sizeof X / sizeof X[0])
     41 
     42 #define MYSQLIDLEN		64
     43 #define MAXQUERYLEN		4096
     44 
     45 typedef union {
     46 	int i;
     47 	unsigned int ui;
     48 	float f;
     49 	const void *v;
     50 } Arg;
     51 
     52 typedef struct {
     53 	void (*cmd)(void);
     54 } Action;
     55 
     56 typedef struct Item Item;
     57 struct Item {
     58 	char **cols;
     59 	int *lens;
     60 	int ncols;
     61 	Item *next;
     62 };
     63 
     64 typedef struct Field Field;
     65 struct Field {
     66 	char name[MYSQLIDLEN];
     67 	int len;
     68 	Field *next;
     69 };
     70 
     71 typedef struct {
     72 	const char *view;
     73 	const int code;
     74 	void (*func)(const Arg *);
     75 	const Arg arg;
     76 } Key;
     77 
     78 typedef struct View View;
     79 struct View {
     80 	char name[16];
     81 	void (*show)(void);
     82 	Item *items;
     83 	Item *choice;
     84 	Field *fields;
     85 	int cur;
     86 	int nitems;
     87 	int nfields;
     88 	struct stfl_form *form;
     89 	View *next;
     90 };
     91 
     92 /* function declarations */
     93 void attach(View *v);
     94 void attachfield(Field *f, Field **ff);
     95 void attachitem(Item *i, Item **ii);
     96 char ui_ask(const char *msg, char *opts);
     97 void cleanup(void);
     98 void cleanupfields(Field **fields);
     99 void cleanupitems(Item **items);
    100 void cleanupview(View *v);
    101 void detach(View *v);
    102 void detachfield(Field *f, Field **ff);
    103 void detachitem(Item *i, Item **ii);
    104 void die(const char *errstr, ...);
    105 void *ecalloc(size_t nmemb, size_t size);
    106 void editfile(char *file);
    107 void editrecord(const Arg *arg);
    108 void edittable(const Arg *arg);
    109 int escape(char *esc, char *s, int sz, char c, char skip);
    110 Item *getitem(int pos);
    111 int *getmaxlengths(Item *items, Field *fields);
    112 void itempos(const Arg *arg);
    113 void mksql_alter_table(char *sql, char *tbl);
    114 void mksql_update(char *sql, Item *item, Field *fields, char *tbl, char *uk);
    115 int mysql_file_exec(char *file);
    116 int mysql_exec(const char *sqlstr, ...);
    117 int mysql_fields(MYSQL_RES *res, Field **fields);
    118 void mysql_fillview(MYSQL_RES *res, int showfds);
    119 int mysql_ukey(char *key, char *tbl, int sz);
    120 int mysql_items(MYSQL_RES *res, Item **items);
    121 void quit(const Arg *arg);
    122 void reload(const Arg *arg);
    123 void run(void);
    124 void setview(const char *name, void (*func)(void));
    125 void setup(void);
    126 void startup(void);
    127 void ui_end(void);
    128 struct stfl_form *ui_getform(wchar_t *code);
    129 void ui_init(void);
    130 void ui_modify(const char *name, const char *mode, const char *fmtstr, ...);
    131 void ui_listview(Item *items, Field *fields);
    132 void ui_putitem(Item *item, int *lens, int id);
    133 void ui_refresh(void);
    134 void ui_set(const char *key, const char *fmtstr, ...);
    135 void ui_showfields(Field *fds, int *lens);
    136 void ui_showitems(Item *items, int *lens);
    137 void ui_sql_edit_exec(char *sql);
    138 void usage(void);
    139 void viewdb(const Arg *arg);
    140 void viewdb_show(void);
    141 void viewdblist(void);
    142 void viewdblist_show(void);
    143 void viewprev(const Arg *arg);
    144 void viewtable(const Arg *arg);
    145 void viewtable_show(void);
    146 
    147 #if defined CTRL && defined _AIX
    148   #undef CTRL
    149 #endif
    150 #ifndef CTRL
    151   #define CTRL(k)   ((k) & 0x1F)
    152 #endif
    153 #define CTRL_ALT(k) ((k) + (129 - 'a'))
    154 
    155 #include "config.h"
    156 
    157 /* variables */
    158 static int running = 1;
    159 static MYSQL *mysql;
    160 static View *views, *selview = NULL;
    161 static struct stfl_ipool *ipool;
    162 static int fldseplen;
    163 
    164 /* function implementations */
    165 void
    166 attach(View *v) {
    167 	v->next = views;
    168 	views = v;
    169 }
    170 
    171 void
    172 attachfield(Field *f, Field **ff) {
    173 	Field **l;
    174 
    175 	for(l = ff; *l && (*l)->next; l = &(*l)->next);
    176 	if(!*l)
    177 		*l = f;
    178 	else
    179 		(*l)->next = f;
    180 }
    181 
    182 void
    183 attachitem(Item *i, Item **ii) {
    184 	Item **l;
    185 
    186 	for(l = ii; *l && (*l)->next; l = &(*l)->next);
    187 	if(!*l)
    188 		*l = i;
    189 	else
    190 		(*l)->next = i;
    191 }
    192 
    193 char
    194 ui_ask(const char *msg, char *opts) {
    195 	int c;
    196 	char *o = NULL;
    197 
    198 	ui_set("status", msg);
    199 	ui_refresh();
    200 	while(!(o && *o) && (c = getch())) {
    201 		if(c == '\n')
    202 			o = &opts[0];
    203 		else
    204 			for(o = opts; *o; ++o)
    205 				if(c == *o)
    206 					break;
    207 	}
    208 	ui_set("status", "");
    209 	return *o;
    210 }
    211 
    212 void
    213 cleanup(void) {
    214 	while(views)
    215 		cleanupview(views);
    216 	ui_end();
    217 	mysql_close(mysql);
    218 }
    219 
    220 void
    221 cleanupview(View *v) {
    222 	detach(v);
    223 	cleanupitems(&v->items);
    224 	cleanupfields(&v->fields);
    225 	if(v->form)
    226 		stfl_free(v->form);
    227 	free(v);
    228 }
    229 
    230 void
    231 cleanupfields(Field **fields) {
    232 	Field *f;
    233 
    234 	while(*fields) {
    235 		f = *fields;
    236 		detachfield(f, fields);
    237 		free(f);
    238 	}
    239 }
    240 
    241 void
    242 cleanupitems(Item **items) {
    243 	Item *i;
    244 
    245 	while(*items) {
    246 		i = *items;
    247 		detachitem(i, items);
    248 		while(--i->ncols >= 0)
    249 			free(i->cols[i->ncols]);
    250 		free(i->cols);
    251 		free(i->lens);
    252 		free(i);
    253 	}
    254 }
    255 
    256 void
    257 detach(View *v) {
    258 	View **tv;
    259 
    260 	for(tv = &views; *tv && *tv != v; tv = &(*tv)->next);
    261 	*tv = v->next;
    262 }
    263 
    264 void
    265 detachfield(Field *f, Field **ff) {
    266 	Field **tf;
    267 
    268 	for(tf = &(*ff); *tf && *tf != f; tf = &(*tf)->next);
    269 	*tf = f->next;
    270 }
    271 
    272 void
    273 detachitem(Item *i, Item **ii) {
    274 	Item **ti;
    275 
    276 	for(ti = &(*ii); *ti && *ti != i; ti = &(*ti)->next);
    277 	*ti = i->next;
    278 }
    279 
    280 void
    281 die(const char *errstr, ...) {
    282 	va_list ap;
    283 
    284 	va_start(ap, errstr);
    285 	vfprintf(stderr, errstr, ap);
    286 	va_end(ap);
    287 	exit(1);
    288 }
    289 
    290 void *
    291 ecalloc(size_t nmemb, size_t size) {
    292 	void *p;
    293 
    294 	if (!(p = calloc(nmemb, size)))
    295 		die("Cannot allocate memory.\n");
    296 	return p;
    297 }
    298 
    299 void
    300 editfile(char *file) {
    301         pid_t pid;
    302 	int rc = -1;
    303 	struct sigaction saold[4];
    304 
    305 	/* take off ncurses signal handlers */
    306 	struct sigaction sa = {.sa_flags = 0, .sa_handler = SIG_DFL};
    307 	sigaction(SIGINT, &sa, &saold[0]);
    308 	sigaction(SIGTERM, &sa, &saold[1]);
    309 	sigaction(SIGTSTP, &sa, &saold[2]);
    310 	sigaction(SIGWINCH, &sa, &saold[3]);
    311 
    312         if((pid = fork()) == 0) {
    313                 execl("/bin/sh", "sh", "-c", "$EDITOR \"$0\"", file, NULL);
    314                 _exit(127);
    315         }
    316 	else if(pid == -1)
    317 		return;
    318 	while(!WIFEXITED(rc))
    319 		waitpid(pid, &rc, 0);
    320 
    321 	/* restore ncurses signal handlers */
    322 	sigaction(SIGINT, &saold[0], NULL);
    323 	sigaction(SIGTERM, &saold[1], NULL);
    324 	sigaction(SIGTSTP, &saold[2], NULL);
    325 	sigaction(SIGWINCH, &saold[3], NULL);
    326 
    327 	/* reinitialize ncurses */
    328 	endwin();
    329 	refresh();
    330 }
    331 
    332 void
    333 editrecord(const Arg *arg) {
    334 	Item *item = getitem(0);
    335 	char *tbl = selview->choice->cols[0], uk[MYSQLIDLEN+1], sql[MAXQUERYLEN+1];
    336 
    337 	if(!item) {
    338 		ui_set("status", "No item selected.");
    339 		return;
    340 	}
    341 	if(mysql_ukey(uk, tbl, sizeof uk)) {
    342 		ui_set("status", "Cannot edit records in `%s`, no unique key found.", tbl);
    343 		return;
    344 	}
    345 	mksql_update(sql, item, selview->fields, tbl, uk);
    346 	ui_sql_edit_exec(sql);
    347 }
    348 
    349 void
    350 edittable(const Arg *arg) {
    351 	Item *item = getitem(0);
    352 	char sql[MAXQUERYLEN+1];
    353 
    354 	if(!item->cols[0]) {
    355 		ui_set("status", "No table selected.");
    356 		return;
    357 	}
    358 	/* XXX check alter table permissions */
    359 	mksql_alter_table(sql, item->cols[0]);
    360 	ui_sql_edit_exec(sql);
    361 }
    362 
    363 int
    364 escape(char *esc, char *s, int sz, char c, char skip) {
    365 	int i, ei = 0;
    366 
    367 	for(i = 0; i < sz; ++i) {
    368 		if(s[i] == c && (!skip || s[i+1] != skip))
    369 			esc[ei++] = '\\';
    370 		esc[ei++] = s[i];
    371 	}
    372 	esc[ei] = '\0';
    373 	return ei - sz;
    374 }
    375 
    376 Item *
    377 getitem(int pos) {
    378 	Item *item;
    379 	int i;
    380 
    381 	if(!selview)
    382 		return NULL;
    383 	if(!pos)
    384 		pos = selview->cur;
    385 	for(item = selview->items, i = 0; item; item = item->next, ++i)
    386 		if(i == pos)
    387 			break;
    388 	return item;
    389 }
    390 
    391 int *
    392 getmaxlengths(Item *items, Field *fields) {
    393 	Item *item;
    394 	Field *fld;
    395 	int i, *lens, ncols;
    396 
    397 	if(!(items || fields))
    398 		return NULL;
    399 	if(items)
    400 		ncols = items->ncols;
    401 	else
    402 		for(fld = fields, ncols = 0; fld; fld = fld->next, ++ncols);
    403 	lens = ecalloc(ncols, sizeof(int));
    404 	if(fields)
    405 		for(fld = fields, i = 0; fld; fld = fld->next, ++i)
    406 			lens[i] = (fld->len <= MAXCOLSZ ? fld->len : MAXCOLSZ);
    407 	if(items)
    408 		for(item = items; item; item = item->next)
    409 			for(i = 0; i < item->ncols; ++i)
    410 				if(lens[i] < item->lens[i])
    411 					lens[i] = (item->lens[i] <= MAXCOLSZ ? item->lens[i] : MAXCOLSZ);
    412 	return lens;
    413 }
    414 
    415 void
    416 itempos(const Arg *arg) {
    417 	int pos;
    418 
    419 	if(!selview || !selview->nitems) {
    420 		ui_set("info", "No items.");
    421 		return;
    422 	}
    423 	pos = selview->cur + arg->i;
    424 	if(pos < 0)
    425 		pos = 0;
    426 	else if(pos >= selview->nitems)
    427 		pos = selview->nitems - 1;
    428 	ui_set("pos", "%d", pos);
    429 	selview->cur = pos;
    430 	ui_set("info", "%d of %d item(s)", selview->cur+1, selview->nitems);
    431 }
    432 
    433 void
    434 mksql_alter_table(char *sql, char *tbl) {
    435 	MYSQL_RES *res;
    436 	MYSQL_ROW row;
    437 	char *p;
    438 	int size = MAXQUERYLEN, len = 0, r;
    439 
    440 	*sql = '\0';
    441 	r = mysql_exec("show create table `%s`", tbl);
    442 	if(r == -1 || !(res = mysql_store_result(mysql)) || !(row = mysql_fetch_row(res)))
    443 		return;
    444 	mysql_free_result(res);
    445 	len += snprintf(&sql[len], size - len + 1, "ALTER TABLE `%s`", tbl);
    446 	for(r = 0, p = &row[1][0]; row[1][r]; ++r) {
    447 		if(row[1][r] != '\n')
    448 			continue;
    449 		while(*p == ' ')
    450 			++p;
    451 		if(*p == '`') {
    452 			row[1][r] = '\0';
    453 			len += snprintf(&sql[len], size - len + 1, "\nMODIFY %s", p);
    454 		}
    455 		p = &row[1][r + 1];
    456 	}
    457 	if(sql[len - 1] == ',')
    458 		sql[len - 1] = '\0';
    459 }
    460 
    461 void
    462 mksql_update(char *sql, Item *item, Field *fields, char *tbl, char *uk) {
    463 	Field *fld;
    464 	char *ukv = NULL, sqlfds[MAXQUERYLEN+1], col[MAXQUERYLEN];
    465 	int size = MAXQUERYLEN, len = 0, i;
    466 
    467 	for(i = 0, fld = fields; fld; fld = fld->next, ++i) {
    468 		if(!ukv && !strncmp(uk, fld->name, fld->len))
    469 			ukv = item->cols[i];
    470 		escape(col, item->cols[i], item->lens[i], '\'', 0);
    471 		len += snprintf(&sqlfds[len], size - len + 1, "\n%c`%s` = '%s'",
    472 			len ? ',' : ' ', fld->name, col);
    473 	}
    474 	snprintf(sql, MAXQUERYLEN+1, "UPDATE `%s` SET%s\nWHERE `%s` = '%s'",
    475 		tbl, sqlfds, uk, ukv);
    476 }
    477 
    478 int
    479 mysql_exec(const char *sqlstr, ...) {
    480 	va_list ap;
    481 	char sql[MAXQUERYLEN+1];
    482 	int r, sqlen;
    483 
    484 	va_start(ap, sqlstr);
    485 	sqlen = vsnprintf(sql, sizeof sql, sqlstr, ap);
    486 	va_end(ap);
    487 	r = mysql_real_query(mysql, sql, sqlen);
    488 	return (r ? -1 : mysql_field_count(mysql));
    489 }
    490 
    491 int
    492 mysql_fields(MYSQL_RES *res, Field **fields) {
    493 	MYSQL_FIELD *fds;
    494 	Field *field;
    495 	int nfds, i;
    496 
    497 	nfds = mysql_num_fields(res);
    498 	if(!nfds)
    499 		return 0;
    500 	fds = mysql_fetch_fields(res);
    501 	for(i = 0; i < nfds; ++i) {
    502 		field = ecalloc(1, sizeof(Field));
    503 		field->len = fds[i].name_length;
    504 		memcpy(field->name, fds[i].name, field->len);
    505 		attachfield(field, fields);
    506 	}
    507 	return nfds;
    508 }
    509 
    510 int
    511 mysql_file_exec(char *file) {
    512 	char buf[MAXQUERYLEN+1], esc[MAXQUERYLEN*2+1];
    513 	int fd, size, r;
    514 
    515 	fd = open(file, O_RDONLY);
    516 	if(fd == -1)
    517 		return -1;
    518 	size = read(fd, buf, MAXQUERYLEN);
    519 	if(size == -1)
    520 		return -2;
    521 	if(!size)
    522 		return 0;
    523 	buf[size] = '\0';
    524 	/* We do not want flow control chars to be interpreted. */
    525 	size += escape(esc, buf, size, '\\', '\'');
    526 	r = mysql_exec(esc);
    527 	if(r == -1)
    528 		return -3;
    529 	return size;
    530 }
    531 
    532 void
    533 mysql_fillview(MYSQL_RES *res, int showfds) {
    534 	cleanupitems(&selview->items);
    535 	selview->nitems = mysql_items(res, &selview->items);
    536 	if(showfds) {
    537 		cleanupfields(&selview->fields);
    538 		selview->nfields = mysql_fields(res, &selview->fields);
    539 	}
    540 }
    541 
    542 int
    543 mysql_ukey(char *key, char *tbl, int sz) {
    544 	MYSQL_RES *res;
    545 	MYSQL_ROW row;
    546 	int r;
    547 
    548 	r = mysql_exec("show keys from `%s` where Non_unique = 0", tbl);
    549 	if(r == -1 || !(res = mysql_store_result(mysql)))
    550 		return 1;
    551 	if(!(row = mysql_fetch_row(res))) {
    552 		mysql_free_result(res);
    553 		return 2;
    554 	}
    555 	snprintf(key, sz, "%s", row[4]);
    556 	mysql_free_result(res);
    557 	return 0;
    558 }
    559 
    560 int
    561 mysql_items(MYSQL_RES *res, Item **items) {
    562 	MYSQL_ROW row;
    563 	Item *item;
    564 	int i, nfds, nrows;
    565 	unsigned long *lens;
    566 
    567 	nfds = mysql_num_fields(res);
    568 	nrows = mysql_num_rows(res);
    569 	*items = NULL;
    570 	while((row = mysql_fetch_row(res))) {
    571 		item = ecalloc(1, sizeof(Item));
    572 		item->lens = ecalloc(nfds, sizeof(int));
    573 		item->cols = ecalloc(nfds, sizeof(char *));
    574 		lens = mysql_fetch_lengths(res);
    575 		item->ncols = nfds;
    576 		for(i = 0; i < nfds; ++i) {
    577 			item->cols[i] = ecalloc(1, lens[i]+1);
    578 			memcpy(item->cols[i], row[i], lens[i]);
    579 			item->lens[i] = lens[i];
    580 		}
    581 		attachitem(item, items);
    582 	}
    583 	return nrows;
    584 }
    585 
    586 void
    587 ui_listview(Item *items, Field *fields) {
    588 	int *lens;
    589 
    590 	if(!selview->form)
    591 		selview->form = ui_getform(FRAG_ITEMS);
    592 	lens = getmaxlengths(items, fields);
    593 	if(fields)
    594 		ui_showfields(fields, lens);
    595 	if(items)
    596 		ui_showitems(items, lens);
    597 	free(lens);
    598 }
    599 
    600 void
    601 ui_showfields(Field *fds, int *lens) {
    602 	Field *fld;
    603 	char line[COLS + 1];
    604 	int li = 0, i, j;
    605 
    606 	if(!(fds && lens))
    607 		return;
    608 	line[0] = '\0';
    609 	for(fld = fds, i = 0; fld && li < COLS; fld = fld->next, ++i) {
    610 		if(i)
    611 			for(j = 0; j < fldseplen && li < COLS; ++j)
    612 				line[li++] = FLDSEP[j];
    613 		for(j = 0; li < COLS && j < fld->len && j < lens[i]; ++j)
    614 			line[li++] = fld->name[j];
    615 		while(li < COLS && j++ < lens[i])
    616 			line[li++] = ' ';
    617 	}
    618 	line[li] = '\0';
    619 	ui_set("subtle", "%s", line);
    620 	ui_set("showsubtle", "%d", (line[0] ? 1 : 0));
    621 }
    622 
    623 void
    624 ui_showitems(Item *items, int *lens) {
    625 	Item *item;
    626 	int id = 0;
    627 
    628 	ui_modify("items", "replace_inner", "vbox"); /* empty items */
    629 	for(item = selview->items; item; item = item->next)
    630 		ui_putitem(item, lens, ++id);
    631 	ui_set("pos", "0");
    632 }
    633 
    634 void
    635 ui_sql_edit_exec(char *sql) {
    636 	struct stat sb, sa;
    637 	int fd;
    638 	char tmpf[] = "/tmp/myadm.XXXXXX";
    639 
    640         fd = mkstemp(tmpf);
    641 	if(fd == -1) {
    642 		ui_set("status", "Cannot make a temporary file.");
    643 		return;
    644 	}
    645         if(write(fd, sql, strlen(sql)) == -1) {
    646 		close(fd);
    647 		unlink(tmpf);
    648 		ui_set("status", "Cannot write into the temporary file.");
    649 		return;
    650 	}
    651 	close(fd);
    652 	stat(tmpf, &sb);
    653 	while(1) {
    654 		editfile(tmpf);
    655 		stat(tmpf, &sa);
    656 		if(!sa.st_size || sb.st_mtime == sa.st_mtime) {
    657 			ui_set("status", "No changes.");
    658 			break;
    659 		}
    660 		if(mysql_file_exec(tmpf) < 0) {
    661 			if(*mysql_error(mysql)) {
    662 				if(ui_ask("An error occurred. Continue editing ([y]/n)?", "yn") == 'y')
    663 					continue;
    664 			}
    665 			else
    666 				ui_set("status", "Something went wrong.");
    667 			break;
    668 		}
    669 		reload(NULL);
    670 		ui_set("status", "Updated.");
    671 		break;
    672 	}
    673 	unlink(tmpf);
    674 }
    675 
    676 void
    677 quit(const Arg *arg) {
    678 	if(arg->i && ui_ask("Do you want to quit ([y]/n)?", "yn") != 'y')
    679 		return;
    680 	running = 0;
    681 }
    682 
    683 void
    684 reload(const Arg *arg) {
    685 	if(!selview->show)
    686 		return;
    687 	selview->show();
    688 	if(selview->cur)
    689 		ui_set("pos", "%d", selview->cur);
    690 }
    691 
    692 void
    693 run(void) {
    694 	int code, i;
    695 
    696 	while(running) {
    697 		ui_refresh();
    698 		code = getch();
    699 		if(code < 0)
    700 			continue;
    701 		for(i = 0; i < LENGTH(keys); ++i) {
    702 			if(ISCURVIEW(keys[i].view) && keys[i].code == code) {
    703 				ui_set("status", "");
    704 				keys[i].func(&keys[i].arg);
    705 				break;
    706 			}
    707 		}
    708 	}
    709 }
    710 
    711 void
    712 setview(const char *name, void (*show)(void)) {
    713 	View *v;
    714 
    715 	v = ecalloc(1, sizeof(View));
    716 	v->choice = getitem(0);
    717 	strncpy(v->name, name, sizeof v->name);
    718 	v->show = show;
    719 	attach(v);
    720 	selview = v;
    721 	show();
    722 }
    723 
    724 void
    725 setup(void) {
    726 	setlocale(LC_CTYPE, "");
    727 	mysql = mysql_init(NULL);
    728 	if(mysql_real_connect(mysql, dbhost, dbuser, dbpass, NULL, 0, NULL, 0) == NULL)
    729 		die("Cannot connect to the database.\n");
    730 	fldseplen = strlen(FLDSEP);
    731 	ui_init();
    732 }
    733 
    734 void
    735 startup(void) {
    736 	for(unsigned int i = 0; i < LENGTH(actions); i++)
    737 		actions[i].cmd();
    738 }
    739 
    740 void
    741 ui_end(void) {
    742 	stfl_reset();
    743 	stfl_ipool_destroy(ipool);
    744 }
    745 
    746 struct stfl_form *
    747 ui_getform(wchar_t *code) {
    748 	return stfl_create(code);
    749 }
    750 
    751 void
    752 ui_init(void) {
    753 	struct stfl_form *f = ui_getform(L"label");
    754 
    755 	stfl_run(f, -3); /* init ncurses */
    756 	stfl_free(f);
    757 	nocbreak();
    758 	raw();
    759 	curs_set(0);
    760 	ipool = stfl_ipool_create(nl_langinfo(CODESET));
    761 }
    762 
    763 void
    764 ui_modify(const char *name, const char *mode, const char *fmtstr, ...) {
    765 	va_list ap;
    766 	char txt[1024];
    767 
    768 	if(!selview->form)
    769 		return;
    770 	va_start(ap, fmtstr);
    771 	vsnprintf(txt, sizeof txt, fmtstr, ap);
    772 	va_end(ap);
    773 	stfl_modify(selview->form,
    774 		stfl_ipool_towc(ipool, name),
    775 		stfl_ipool_towc(ipool, mode),
    776 		stfl_ipool_towc(ipool, txt));
    777 }
    778 
    779 void
    780 ui_putitem(Item *item, int *lens, int id) {
    781 	char line[COLS + 1];
    782 	int pad, li = 0, i, j;
    783 
    784 	if(!(item && lens))
    785 		return;
    786 	line[0] = '\0';
    787 	for(i = 0; i < item->ncols && li < COLS; ++i) {
    788 		if(i)
    789 			for(j = 0; j < fldseplen && li < COLS; ++j)
    790 				line[li++] = FLDSEP[j];
    791 		pad = li;
    792 		for(j = 0; j < item->lens[i] && j < lens[i] && li < COLS; ++j)
    793 			line[li++] = (isprint(item->cols[i][j])
    794 					? item->cols[i][j]
    795 					: ' ');
    796 		pad = li - pad;
    797 		while(pad++ < lens[i] && li < COLS)
    798 			line[li++] = ' ';
    799 	}
    800 	line[li] = '\0';
    801 	ui_modify("items", "append", "listitem[%d] text:%s", id, QUOTE(line));
    802 }
    803 
    804 void
    805 ui_refresh(void) {
    806 	if(selview && selview->form)
    807 		stfl_run(selview->form, -1);
    808 }
    809 
    810 void
    811 ui_set(const char *key, const char *fmtstr, ...) {
    812 	va_list ap;
    813 	char val[256];
    814 
    815 	if(!selview)
    816 		return;
    817 
    818 	va_start(ap, fmtstr);
    819 	vsnprintf(val, sizeof val, fmtstr, ap);
    820 	va_end(ap);
    821 	stfl_set(selview->form, stfl_ipool_towc(ipool, key), stfl_ipool_towc(ipool, val));
    822 }
    823 
    824 void
    825 usage(void) {
    826 	die("Usage: %s [-vhup <arg>]\n", argv0);
    827 }
    828 
    829 void
    830 viewdb(const Arg *arg) {
    831 	Arg a = {.i = 0};
    832 	Item *choice = getitem(0);
    833 
    834 	if(!choice) {
    835 		ui_set("status", "No database selected.");
    836 		return;
    837 	}
    838 	mysql_select_db(mysql, choice->cols[0]);
    839 	setview("tables", viewdb_show);
    840 	itempos(&a);
    841 }
    842 
    843 void
    844 viewdb_show(void) {
    845 	MYSQL_RES *res;
    846 
    847 	if(mysql_exec("show tables") == -1 || !(res = mysql_store_result(mysql)))
    848 		die("show tables");
    849 	mysql_fillview(res, 0);
    850 	mysql_free_result(res);
    851 	ui_listview(selview->items, NULL);
    852 	ui_set("title", "Tables in `%s`@%s", selview->choice->cols[0], dbhost);
    853 }
    854 
    855 void
    856 viewdblist(void) {
    857 	Arg a = {.i = 0};
    858 
    859 	setview("databases", viewdblist_show);
    860 	itempos(&a);
    861 }
    862 
    863 void
    864 viewdblist_show(void) {
    865 	MYSQL_RES *res;
    866 
    867 	if(mysql_exec("show databases") == -1 || !(res = mysql_store_result(mysql)))
    868 		die("show databases");
    869 	mysql_fillview(res, 0);
    870 	mysql_free_result(res);
    871 	ui_listview(selview->items, NULL);
    872 	ui_set("title", "Databases in `%s`", dbhost);
    873 }
    874 
    875 void
    876 viewprev(const Arg *arg) {
    877 	View *v;
    878 
    879 	if(!(selview && selview->next))
    880 		return;
    881 	v = selview->next;
    882 	cleanupview(selview);
    883 	selview = v;
    884 }
    885 
    886 void
    887 viewtable(const Arg *arg) {
    888 	Arg a = {.i = 0};
    889 
    890 	if(!getitem(0)) {
    891 		ui_set("status", "No table selected.");
    892 		return;
    893 	}
    894 	setview("records", viewtable_show);
    895 	itempos(&a);
    896 }
    897 
    898 void
    899 viewtable_show(void) {
    900 	MYSQL_RES *res;
    901 	int r;
    902 
    903 	r = mysql_exec("select * from `%s`", selview->choice->cols[0]);
    904 	if(r == -1 || !(res = mysql_store_result(mysql)))
    905 		die("select from `%s`", selview->choice->cols[0]);
    906 	mysql_fillview(res, 1);
    907 	mysql_free_result(res);
    908 	ui_listview(selview->items, selview->fields);
    909 	ui_set("title", "Records in `%s`.`%s`@%s",
    910 		selview->next->choice->cols[0], selview->choice->cols[0], dbhost);
    911 }
    912 
    913 int
    914 main(int argc, char **argv) {
    915 	ARGBEGIN {
    916 	case 'h':
    917 		dbhost = EARGF(usage());
    918 		break;
    919 	case 'u':
    920 		dbuser = EARGF(usage());
    921 		break;
    922 	case 'p':
    923 		dbpass = EARGF(usage());
    924 		break;
    925 	case 'v':
    926 		die("%s-"VERSION, argv0);
    927 	default:
    928 		usage();
    929 	} ARGEND;
    930 	setup();
    931 	startup();
    932 	run();
    933 	cleanup();
    934 	return 0;
    935 }