/* SylFilter - a message filter
 *
 * Copyright (C) 2011 Hiroyuki Yamamoto
 * Copyright (C) 2011 Sylpheed Development Team
 */

#include "config.h"

#ifdef USE_GDBM

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <glib.h>
#include <gdbm.h>

#include "filter-kvs.h"
#include "filter-kvs-gdbm.h"

static XFilterKVS *xf_gdbm_open(const char *dbfile);
static int xf_gdbm_close(XFilterKVS *kvs);
static int xf_gdbm_insert(XFilterKVS *kvs, const char *key, void *value, int size);
static int xf_gdbm_delete(XFilterKVS *kvs, const char *key);
static int xf_gdbm_update(XFilterKVS *kvs, const char *key, void *value, int size);
static int xf_gdbm_fetch(XFilterKVS *kvs, const char *key, void *vbuf, int vsize);
static int xf_gdbm_size(XFilterKVS *kvs);
static int xf_gdbm_foreach(XFilterKVS *kvs, XFilterKVSForeachFunc func, void *data);


int xfilter_kvs_gdbm_set_engine(void)
{
	XFilterKVSEngine engine = {
		xf_gdbm_open,
		xf_gdbm_close,
		xf_gdbm_insert,
		xf_gdbm_delete,
		xf_gdbm_update,
		xf_gdbm_fetch,
		NULL,
		NULL,
		xf_gdbm_size,
		xf_gdbm_foreach
	};

	return xfilter_kvs_set_engine(&engine);
}

static XFilterKVS *xf_gdbm_open(const char *dbfile)
{
	GDBM_FILE dbf;

	dbf = gdbm_open((char *)dbfile, 0, GDBM_WRCREAT, 0600, 0);
	if (!dbf) {
		fprintf(stderr, "xf_gdbm_open: %s: %s\n",
			dbfile, gdbm_strerror(gdbm_errno));
		return NULL;
	}
	return xfilter_kvs_new(dbfile, (void *)dbf);
}

static int xf_gdbm_close(XFilterKVS *kvs)
{
	gdbm_close((GDBM_FILE)xfilter_kvs_get_handle(kvs));
	return 0;
}

static int xf_gdbm_insert(XFilterKVS *kvs, const char *key, void *value, int size)
{
	GDBM_FILE dbf;
	datum dkey, dvalue;

	dbf = (GDBM_FILE)xfilter_kvs_get_handle(kvs);

	dkey.dptr = (char *)key;
	dkey.dsize = strlen(key);
	dvalue.dptr = (char *)value;
	dvalue.dsize = size;
	if (gdbm_store(dbf, dkey, dvalue, GDBM_INSERT) != 0)
		return -1;

	return 0;
}

static int xf_gdbm_delete(XFilterKVS *kvs, const char *key)
{
	GDBM_FILE dbf;
	datum dkey;

	dbf = (GDBM_FILE)xfilter_kvs_get_handle(kvs);

	dkey.dptr = (char *)key;
	dkey.dsize = strlen(key);
	if (gdbm_delete(dbf, dkey) != 0)
		return -1;

	return 0;
}

static int xf_gdbm_update(XFilterKVS *kvs, const char *key, void *value, int size)
{
	GDBM_FILE dbf;
	datum dkey, dvalue;

	dbf = (GDBM_FILE)xfilter_kvs_get_handle(kvs);

	dkey.dptr = (char *)key;
	dkey.dsize = strlen(key);
	dvalue.dptr = (char *)value;
	dvalue.dsize = size;
	if (gdbm_store(dbf, dkey, dvalue, GDBM_REPLACE) != 0)
		return -1;

	return 0;
}

static int xf_gdbm_fetch(XFilterKVS *kvs, const char *key, void *vbuf, int vsize)
{
	GDBM_FILE dbf;
	datum dkey, dvalue;

	dbf = (GDBM_FILE)xfilter_kvs_get_handle(kvs);

	dkey.dptr = (char *)key;
	dkey.dsize = strlen(key);
	dvalue = gdbm_fetch(dbf, dkey);
	if (!dvalue.dptr)
		return -1;

	memcpy(vbuf, dvalue.dptr, MIN(vsize, dvalue.dsize));
	free(dvalue.dptr);

	return dvalue.dsize;
}

static int xf_gdbm_size(XFilterKVS *kvs)
{
	GDBM_FILE dbf;
	datum dkey, nkey;
	int size = 0;

	dbf = (GDBM_FILE)xfilter_kvs_get_handle(kvs);

	for (dkey = gdbm_firstkey(dbf); dkey.dptr; size++) {
		nkey = gdbm_nextkey(dbf, dkey);
		free(dkey.dptr);
		dkey = nkey;
	}

	return size;
}

static int xf_gdbm_foreach(XFilterKVS *kvs, XFilterKVSForeachFunc func, void *data)
{
	GDBM_FILE dbf;
	datum dkey, dvalue, nkey;
	char key[1024];
	int ksize;

	dbf = (GDBM_FILE)xfilter_kvs_get_handle(kvs);

	dkey = gdbm_firstkey(dbf);
	for (dkey = gdbm_firstkey(dbf); dkey.dptr; ) {
		int r;

		dvalue = gdbm_fetch(dbf, dkey);
		if (!dvalue.dptr) {
			free(dkey.dptr);
			break;
		}
		ksize = MIN(sizeof(key) - 1, dkey.dsize);
		memcpy(key, dkey.dptr, ksize);
		key[ksize] = '\0';
		r = func(kvs, key, dvalue.dptr, dvalue.dsize, data);
		if (r < 0) {
			free(dvalue.dptr);
			free(dkey.dptr);
			break;
		}
		nkey = gdbm_nextkey(dbf, dkey);
		free(dvalue.dptr);
		free(dkey.dptr);

		dkey = nkey;
	}

	return 0;
}

#endif /* USE_GDBM */
