/*****************************************************************************

NAME:
datastore_qdbm.c -- implements the datastore, using qdbm.

AUTHORS:
Gyepi Sam <gyepi@praxis-sw.com>          2003
Matthias Andree <matthias.andree@gmx.de> 2003
Stefan Bellon <sbellon@sbellon.de>       2003-2004

******************************************************************************/

#include "common.h"

#include <depot.h>
#include <cabin.h>
#include <villa.h>
#include <stdlib.h>

#include "datastore.h"
#include "datastore_db.h"
#include "datastore_qdbm.h"
#include "error.h"
#include "paths.h"
#include "xmalloc.h"
#include "xstrdup.h"

#define UNUSED(x) ((void)&x)

typedef struct {
    char *path;
    char *name;
    bool locked;
    bool created;
    VILLA *dbp;
} dbh_t;

/* Function definitions */

const char *db_version_str(void)
{
    static char v[80];
    if (!v[0])
	snprintf(v, sizeof(v), "QDBM (Depot version %s, Villa API)", dpversion);
    return v;
}


static dbh_t *dbh_init(bfpath *bfp)
{
    dbh_t *handle;

    handle = (dbh_t *)xmalloc(sizeof(dbh_t));
    memset(handle, 0, sizeof(dbh_t));	/* valgrind */

    handle->name = xstrdup(bfp->filepath);

    handle->locked = false;
    handle->created = false;

    return handle;
}


static void dbh_free(/*@only@*/ dbh_t *handle)
{
    if (handle != NULL) {
      xfree(handle->name);
      xfree(handle->path);
      xfree(handle);
    }
    return;
}


/* Returns is_swapped flag */
bool db_is_swapped(void *vhandle)
{
    UNUSED(vhandle);

    return false;
}


/* Returns created flag */
bool db_created(void *vhandle)
{
    dbh_t *handle = (dbh_t *)vhandle;
    return handle->created;
}


/*
  Initialize database.
  Returns: pointer to database handle on success, NULL otherwise.
*/
void *db_open(void * dummy, bfpath *bfp, dbmode_t open_mode)
{
    dbh_t *handle;

    int open_flags;
    VILLA *dbp;

    UNUSED(dummy);

    if (open_mode & DS_WRITE)
	open_flags = VL_OWRITER;
    else
	open_flags = VL_OREADER;

    handle = dbh_init(bfp);

    if (handle == NULL) return NULL;

    dbp = handle->dbp = vlopen(handle->name, open_flags, cmpkey);

    if ((dbp == NULL) && (open_mode & DS_WRITE)) {
	dbp = handle->dbp = vlopen(handle->name, open_flags|VL_OCREAT, cmpkey);
	if (dbp != NULL)
	    handle->created = true;
    }

    if (dbp == NULL)
	goto open_err;

    if (DEBUG_DATABASE(1))
	fprintf(dbgout, "(qdbm) vlopen( %s, %d )\n", handle->name, open_mode);

    return handle;

 open_err:
    print_error(__FILE__, __LINE__, "(qdbm) vlopen(%s, %d), err: %d, %s",
		handle->name, open_flags, 
		dpecode, dperrmsg(dpecode));
    dbh_free(handle);

    return NULL;
}


int db_delete(void *vhandle, const dbv_t *token)
{
    int ret;
    dbh_t *handle = (dbh_t *)vhandle;
    VILLA *dbp;

    dbp = handle->dbp;
    ret = vlout(dbp, (char *)token->data, token->leng);

    if (ret == 0) {
	print_error(__FILE__, __LINE__, "(qdbm) vlout('%.*s'), err: %d, %s",
		    CLAMP_INT_MAX(token->leng),
		    (char *)token->data, 
		    dpecode, dperrmsg(dpecode));
	exit(EX_ERROR);
    }
    ret = ret ^ 1;	/* ok is 1 in qdbm and 0 in bogofilter */

    return ret;		/* 0 if ok */
}


int db_get_dbvalue(void *vhandle, const dbv_t *token, /*@out@*/ dbv_t *val)
{
    char *data;
    int dsiz;

    dbh_t *handle = (dbh_t *)vhandle;
    VILLA *dbp = handle->dbp;

    data = vlget(dbp, (const char *)token->data, token->leng, &dsiz);

    if (data == NULL)
	return DS_NOTFOUND;

    if (val->leng < (unsigned)dsiz) {
	print_error(__FILE__, __LINE__,
		    "(qdbm) db_get_dbvalue( '%.*s' ), size error %lu: %lu",
		    CLAMP_INT_MAX(token->leng),
		    (char *)token->data, (unsigned long)val->leng,
		    (unsigned long)dsiz);
	exit(EX_ERROR);
    }

    val->leng = dsiz;		/* read count */
    memcpy(val->data, data, dsiz);

    free(data); /* not xfree() as allocated by vlget() */

    return 0;
}


/*
   Re-organize database according to some heuristics
*/
static inline void db_optimize(VILLA *dbp, char *name)
{
    UNUSED(dbp);
    UNUSED(name);

    /* The Villa API doesn't need optimizing like the formerly used
       Depot API because Villa uses B+ trees and Depot uses hash tables.
       Database size may grow larger and could get compacted with
       vloptimize() however as the database size with Villa is smaller
       anyway, I don't think it is worth it. -- David Relson */
}


int db_set_dbvalue(void *vhandle, const dbv_t *token, const dbv_t *val)
{
    int ret;
    dbh_t *handle = (dbh_t *)vhandle;
    VILLA *dbp = handle->dbp;

    ret = vlput(dbp, (const char *)token->data, token->leng, (const char *)val->data, val->leng, VL_DOVER);

    if (ret == 0) {
	print_error(__FILE__, __LINE__,
		    "(qdbm) db_set_dbvalue( '%.*s' ) err: %d, %s",
		    CLAMP_INT_MAX(token->leng), (char *)token->data,
		    dpecode, dperrmsg(dpecode));
	exit(EX_ERROR);
    }

    db_optimize(dbp, handle->name);

    return 0;
}


/*
   Close files and clean up.
*/
void db_close(void *vhandle)
{
    dbh_t *handle = (dbh_t *)vhandle;
    VILLA *dbp;

    if (handle == NULL) return;

    if (DEBUG_DATABASE(1))
	fprintf(dbgout, "(qdbm) vlclose(%s)\n", handle->name);

    dbp = handle->dbp;

    db_optimize(dbp, handle->name);

    if (!vlclose(dbp))
	print_error(__FILE__, __LINE__, "(qdbm) vlclose for %s err: %d, %s",
		    handle->name, 
		    dpecode, dperrmsg(dpecode));

    handle->dbp = NULL;

    dbh_free(handle);
}


/*
   Flush any data in memory to disk
*/
void db_flush(void *vhandle)
{
    dbh_t *handle = (dbh_t *)vhandle;
    VILLA * dbp = handle->dbp;

    if (!vlsync(dbp))
	print_error(__FILE__, __LINE__, "(qdbm) vlsync err: %d, %s",
		    dpecode, dperrmsg(dpecode));
}


ex_t db_foreach(void *vhandle, db_foreach_t hook, void *userdata)
{
    int ret = 0;

    dbh_t *handle = (dbh_t *)vhandle;
    VILLA *dbp = handle->dbp;

    dbv_t dbv_key;
    dbv_const_t dbv_data;
    int ksiz, dsiz;
    char *key, *data;

    ret = vlcurfirst(dbp);
    if (ret) {
	while ((key = vlcurkey(dbp, &ksiz))) {
	    data = vlcurval(dbp, &dsiz);
	    if (data) {
		/* switch to "dbv_t *" variables */
		dbv_key.leng = ksiz;
		dbv_key.data = xmalloc(dbv_key.leng+1);
		memcpy(dbv_key.data, key, ksiz);
		((char *)dbv_key.data)[dbv_key.leng] = '\0';

		dbv_data.data = data;
		dbv_data.leng = dsiz;		/* read count */

		/* call user function */
		ret = hook(&dbv_key, &dbv_data, userdata);

		xfree(dbv_key.data);

		if (ret != 0)
		    break;
		free(data); /* not xfree() as allocated by dpget() */
	    }
	    free(key); /* not xfree() as allocated by dpiternext() */

	    vlcurnext(dbp);
	}
    } else {
	print_error(__FILE__, __LINE__, "(qdbm) vlcurfirst err: %d, %s",
		    dpecode, dperrmsg(dpecode));
	exit(EX_ERROR);
    }

    return EX_OK;
}

const char *db_str_err(int e)
{
    return dperrmsg(e);
}
