/*
Copyright (c) 2007-2010, Dirk Krause
All rights reserved.

Redistribution and use in source and binary forms,
with or without modification, are permitted provided
that the following conditions are met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.
* Redistributions in binary form must reproduce the above 
  opyright notice, this list of conditions and the following
  disclaimer in the documentation and/or other materials
  provided with the distribution.
* Neither the name of the Dirk Krause nor the names of
  contributors may be used to endorse or promote
  products derived from this software without specific
  prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/



/**	@file	dksdbi.c	Simple database API.
*/



#include "dk.h"

#if DK_HAVE_STRING_H
#include <string.h>
#endif
#if DK_HAVE_FCNTL_H
#include <fcntl.h>
#endif
#if DK_HAVE_IO_H
#include <io.h>
#endif

#include "dkmem.h"
#include "dkstr.h"
#include "dksfc.h"
#include "dksf.h"



/**	Inside the dksdbi module.
*/
#define DKSDBI_C	1

#include "dksdbi.h"
#include "dksdbii.h"




#line 73 "dksdbi.ctr"




/**	DB type: Berkeley DB.
*/
static char str_bdb[]  = { "bdb:"  };

/**	DB type: NDBM.
*/
static char str_ndbm[] = { "ndbm:" };

/**	DB file suffixes: dir for NDBM files.
*/
static char suffix_dir[] = { ".dir" };

/**	DB file suffixes: pag for NDBM files.
*/
static char suffix_pag[] = { ".pag" };



/**	Connect prefix and database type.
*/
typedef struct {
  char *prefix;	/**< Prefix. */
  int db_type;	/**< Database type. */
} _prefix_info_t_ ;

/**	Map DB type names to numeric constants.
*/
_prefix_info_t_ prefix_info[] = {
  { str_bdb,  DK_SDBI_TYPE_BDB},
  { str_ndbm, DK_SDBI_TYPE_NDBM},
  {NULL, 0}
};



/**	Translate file mode from DK_PERM_xxx to native constants.
	@param	i	DK_PERM_xxx file mode.
	@return	Native file mode.
*/
static mode_t
translate_file_mode(int i)
{
  mode_t back = 0;
  if(i) {
    if(i & DK_PERM_SUID) back |= 04000;
    if(i & DK_PERM_SGID) back |= 02000;
    if(i & DK_PERM_VTX) back |= 01000;
    if(i & DK_PERM_U_READ) back |= 0400;
    if(i & DK_PERM_U_WRITE) back |= 0200;
    if(i & DK_PERM_U_EXECUTE) back |= 0100;
    if(i & DK_PERM_G_READ) back |= 040;
    if(i & DK_PERM_G_WRITE) back |= 020;
    if(i & DK_PERM_G_EXECUTE) back |= 010;
    if(i & DK_PERM_O_READ) back |= 04;
    if(i & DK_PERM_O_WRITE) back |= 02;
    if(i & DK_PERM_O_EXECUTE) back |= 01;
  } else {
    back = 0600;
  }
  return back;
}



/**	Close the database.
	@param	p	Simple database API structure.
*/
static
void
do_close DK_P1(dk_sdbi_db *,p)
{
  
  switch(p->tp) {
    case DK_SDBI_TYPE_BDB: {
#if DK_HAVE_DB_H
      dksdbi_bdb_close(p);
#endif
    } break;
    case DK_SDBI_TYPE_NDBM: {
#if DK_HAVE_NDBM_H
      dksdbi_ndbm_close(p);
#endif
    } break;
  }
  
}



void
dksdbi_close DK_P1(dk_sdbi_t,p)
{
  
  if(p) {
    do_close((dk_sdbi_db *)p);
  }
  
}



/**	Initialize a simple database API structure.
	@param	p	API structure to initialize.
*/
static void
db_init_empty DK_P1(dk_sdbi_db *,p)
{
  
  p->buffer = NULL;
  p->fn = NULL;
  p->tp = 0;
  p->acc = 0;
  p->fm = 0;
  p->blksize = 0;
  p->did_truncate = 0;
  p->dbptr = NULL;
  
}



/**	Retrieve preferred database type.
	@return	The preferred underlaying mechanism, DK_SDBI_TYPE_xxx.
*/
static
int
get_preferred_dk_sdbi_dbtype DK_P0() 
{
  int back = 0;
#if DK_HAVE_DB_H
  back = DK_SDBI_TYPE_BDB;
#else
#if DK_HAVE_NDBM_H
#if !DK_HAVE_GDBM_H
  back = DK_SDBI_TYPE_NDBM;
#endif
#endif
#endif
  return back;
}



/**	Correct name and type.
	@param	p	Simple database API structure.
*/
static void
correct_name_and_type DK_P1(dk_sdbi_db *,p)
{
  size_t l;
  
  if(p->tp == DK_SDBI_TYPE_AUTO) {
    _prefix_info_t_ *pptr;
    pptr = prefix_info;
    while((pptr->prefix) && (p->tp == DK_SDBI_TYPE_AUTO)) {
      
      l = strlen(pptr->prefix);
      if(strlen(p->buffer) > l) {
        if(strncmp(pptr->prefix, p->buffer, l) == 0) {
	  
	  p->tp = pptr->db_type;
	  p->fn = &((p->buffer)[l]);
	}
      }
      pptr++;
    }
    if(p->tp == DK_SDBI_TYPE_AUTO) {
      p->tp = get_preferred_dk_sdbi_dbtype();
    }
  } 
  
}



/**	Attempt to open database.
	@param	p	Simple database API structure.
	@return	1 on success, 0 on error.
*/
static int
attempt_to_open DK_P1(dk_sdbi_db *,p)
{
  int back = 0;
  
  switch(p->tp) {
    case DK_SDBI_TYPE_BDB: {
#if DK_HAVE_DB_H
      back = dksdbi_bdb_open(p);
#endif
    } break;
    case DK_SDBI_TYPE_NDBM: {
#if DK_HAVE_NDBM_H
      back = dksdbi_ndbm_open(p);
#endif
    } break;
  }
  
  return back;
}



dk_sdbi_t
dksdbi_open DK_P5(char *,fn, int,tp, int,acc, int,fm, int,blksize)
{
  dk_sdbi_db *back = NULL;
  
  if(fn) {
    back = dk_new(dk_sdbi_db,1);
    if(back) {
      db_init_empty(back);
      back->buffer = dkstr_dup(fn);
      if(back->buffer) {
        back->fn = back->buffer;
	back->tp = tp;
	back->acc = acc;
	back->fm = fm;
	back->tfm = translate_file_mode(fm);
	back->blksize = blksize;
	correct_name_and_type(back);
	if(back->tp != DK_SDBI_TYPE_AUTO) {
	  if(attempt_to_open(back)) {
	  } else {		
	    dksdbi_close(back);
	    back = NULL;
	  }
	} else {		
	  dksdbi_close(back);
	  back = NULL;
	}
      } else {			
        dksdbi_close(back);
	back = NULL;
      }
    } else {			
    }
  } else {			
  }
  
  return((dk_sdbi_t)back);
}



/**	Save new DB entry.
	@param	p	Simple database API structure.
	@param	kp	Key pointer.
	@param	kl	Key length.
	@param	vp	Value pointer.
	@param	vl	value length.
	@param	insmod	Insertion mode.
	@return	1 on success, 0 on error.
*/
static
int
do_store DK_P6(dk_sdbi_db *,p, void *,kp, size_t,kl, void *,vp, size_t,vl, int,insmod)
{
  int back = 0;
  
  if((p->acc) & DK_SDBI_MODE_WRITE) {
  switch(p->tp) {
    case DK_SDBI_TYPE_BDB: {
#if DK_HAVE_DB_H
      back = dksdbi_bdb_store(p, kp, kl, vp, vl, insmod);
#endif
    } break;
    case DK_SDBI_TYPE_NDBM: {
#if DK_HAVE_NDBM_H
      back = dksdbi_ndbm_store(p, kp, kl, vp, vl, insmod);
#endif
    } break;
  }
  }
  
  return back;
}



int
dksdbi_store DK_P6(dk_sdbi_t,p, void *,kp, size_t,kl, void *,vp, size_t,vl, int,insmod)
{
  int back = 0;
  
  if((p) && (kp) && (kl) && (vp) && (vl)) {
    back = do_store((dk_sdbi_db *)p, kp, kl, vp, vl, insmod);
  }
  
  return back;
}



/**	Store a string entry into the database.
	@param	p	Simple database API structure.
	@param	k	Key name.
	@param	v	Value.
	@param	insm	Insertion mode (allow overwrite or not).
	@return	1 on success, 0 on error.
*/
static
int
do_string_store DK_P4(dk_sdbi_db *,p, char *,k, char *,v, int,insm)
{
  int back = 0;
  size_t l1, l2;
  
  if((p->acc) & DK_SDBI_MODE_WRITE) {
    l1 = 1 + strlen(k); l2 = 1 + strlen(v);
    back = do_store(p, (void *)k, l1, (void *)v, l2, insm);
  }
  
  return back;
}



/**	Delete a DB entry.
	@param	p	Simple database API structure.
	@param	k	Key to delete.
	@param	kl	Size of \a k.
	@return	1 on success, 0 on error.
*/
static
int
do_delete DK_P3(dk_sdbi_db *,p, char *,k, size_t,kl)
{
  int back = 0;
  
  if((p->acc) & DK_SDBI_MODE_WRITE) {
  switch(p->tp) {
    case DK_SDBI_TYPE_BDB: {
#if DK_HAVE_DB_H
      back = dksdbi_bdb_delete(p, k, kl);
#endif
    } break;
    case DK_SDBI_TYPE_NDBM: {
#if DK_HAVE_NDBM_H
      back = dksdbi_ndbm_delete(p, k, kl);
#endif
    } break;
  }
  }
  
  return back;
}



/**	Delete a string entry from the database.
	@param	p	Simple database API structure.
	@param	k	Key name.
	@return	1 on success, 0 on error.
*/
static
int
do_string_delete DK_P2(dk_sdbi_db *,p, char *,k)
{
  int back = 0;
  size_t l1;
  
  if((p->acc) & DK_SDBI_MODE_WRITE) {
    l1 = 1 + strlen(k);
    back = do_delete(p, (void *)k, l1);
  }
  
  return back;
}



int
dksdbi_string_store DK_P4(dk_sdbi_t,p, char *,k, char *,v, int,insm)
{
  int back = 0;
  
  if((p) && (k)) {
    if(v) {
      back = do_string_store((dk_sdbi_db *)p, k, v, insm);
    } else {
      do_string_delete((dk_sdbi_db *)p, k);
    }
  }
  
  return back;
}



/**	Fetch entry from database.
	@param	p	Database.
	@param	kp	Key pointer.
	@param	kl	Key length.
	@param	vp	Value buffer pointer.
	@param	vl	Value length.
	@return	1 on success, 0 on error.
*/
static
int
do_fetch DK_P5(dk_sdbi_db *,p, void *,kp, size_t,kl, void *,vp, size_t *,vl)
{
  int back = 0;
  
  if((p->acc) & DK_SDBI_MODE_READ) {
  switch(p->tp) {
    case DK_SDBI_TYPE_BDB: {
#if DK_HAVE_DB_H
      back = dksdbi_bdb_fetch(p, kp, kl, vp, vl);
#endif
    } break;
    case DK_SDBI_TYPE_NDBM: {
#if DK_HAVE_NDBM_H
      back = dksdbi_ndbm_fetch(p, kp, kl, vp, vl);
#endif
    } break;
  }
  }
  
  return back;
}



int
dksdbi_fetch DK_P5(dk_sdbi_t,p, void *,kp, size_t,kl, void *,vp, size_t *,vl)
{
  int back = 0;
  
  if((p) && (kp) && (kl) && (vp) && (vl)) {
    back = do_fetch((dk_sdbi_db *)p, kp, kl, vp, vl);
  }
  
  return back;
}



/**	Fetch string from database.
	@param	p	Database.
	@param	k	Key.
	@param	v	Result buffer.
	@param	s	Length of \a s.
	@return	1 on success, 0 on error.
*/
static
int
do_string_fetch DK_P4(dk_sdbi_db *,p, char *,k, char *,v, size_t,s)
{
  int back = 0;
  
  if((p->acc) & DK_SDBI_MODE_READ) {
  switch(p->tp) {
    case DK_SDBI_TYPE_BDB: {
#if DK_HAVE_DB_H
      back = dksdbi_bdb_string_fetch(p, k, v, s);
#endif
    } break;
    case DK_SDBI_TYPE_NDBM: {
#if DK_HAVE_NDBM_H
      back = dksdbi_ndbm_string_fetch(p, k, v, s);
#endif
    } break;
  }
  }
  
  return back;
}

int
dksdbi_string_fetch DK_P4(dk_sdbi_t,p, char *,k, char *,v, size_t,s)
{
  int back = 0;
  
  if((p) && (k) && (v) && (s)) {
    back = do_string_fetch((dk_sdbi_db *)p, k, v, s);
  }
  
  return back;
}



int
dksdbi_delete DK_P3(dk_sdbi_t,p, char *,k, size_t,kl)
{
  int back = 0;
  if((p) && (k) && (kl)) { do_delete((dk_sdbi_db *)p, k, kl); }
  return back;
}



int
dksdbi_string_delete DK_P2(dk_sdbi_t,p, char *,k)
{
  int back = 0;
  
  if((p) && (k)) { do_string_delete((dk_sdbi_db *)p, k); }
  
  return back;
}



int
dksdbi_remove_file DK_P2(char *,fn, int,tp)
{
  int		back = 0;
  int		t = 0;
  char		*f = NULL;
  size_t	pl;
  _prefix_info_t_	*pptr;
  t = tp; f = fn;
  if(fn) {
    if(t == DK_SDBI_TYPE_AUTO) {
      pptr = prefix_info;
      while((pptr->prefix) && (t == DK_SDBI_TYPE_AUTO)) {
        pl = strlen(pptr->prefix);
	if(strlen(fn) > pl) {
	  if(strncmp(pptr->prefix, fn, pl) == 0) {
	    t = pptr->db_type;
	    f = &((fn)[pl]);
	  }
	}
	pptr++;
      }
      if(t == DK_SDBI_TYPE_AUTO) {
        t = get_preferred_dk_sdbi_dbtype();
      }
    }
    switch(t) {
      case DK_SDBI_TYPE_NDBM: {
        int i1, i2; size_t sz; char *mybuffer;
	sz = strlen(f) + 5;
	mybuffer = dk_new(char,sz);
	if(mybuffer) {
	  i1 = i2 = 0;
	  strcpy(mybuffer, f); strcat(mybuffer, suffix_dir);
	  i1 = dksf_remove_file(mybuffer);
	  strcpy(mybuffer, f); strcat(mybuffer, suffix_pag);
	  i2 = dksf_remove_file(mybuffer);
	  if((i1) && (i2)) {
	    back = 1;
	  }
	  dk_delete(mybuffer);
	}
      } break;
      default: {
        back = dksf_remove_file(f);
      } break;
    }
  }
  return back;
}



/**	Traverse a database.
	@param	p	Simple database API structure.
	@param	d	Object to use during traversal.
	@param	f	Function to use for traversal.
	@return	1 on success, 0 on error.
*/
static
int
do_traverse DK_P3(dk_sdbi_db *,p, void *,d, dk_sdbi_fct_t *,f)
{
  int back = 0;
  
  if((p->acc) & DK_SDBI_MODE_READ) {
  switch(p->tp) {
    case DK_SDBI_TYPE_BDB: {
#if DK_HAVE_DB_H
      back = dksdbi_bdb_traverse(p, d, f);
#endif
    } break;
    case DK_SDBI_TYPE_NDBM: {
#if DK_HAVE_NDBM_H
      back = dksdbi_ndbm_traverse(p, d, f);
#endif
    } break;
  }
  }
  
  return back;
}



int
dksdbi_traverse DK_P3(dk_sdbi_t,p, void *,d, dk_sdbi_fct_t *,f)
{
  int back = 0;
  
  if((p) && (f)) { back = do_traverse((dk_sdbi_db *)p, d, f); }
  
  return back;
}



/**	Synchronize DB to disk if supported by underlaying mechanisms.
	@param	p	Simple database API structure.
	@return	1 on success, 0 on error.
*/
static
int
do_sync DK_P1(dk_sdbi_db *,p)
{
  int back = 0;
  
  if((p->acc) & DK_SDBI_MODE_WRITE) {
  switch(p->tp) {
    case DK_SDBI_TYPE_BDB: {
#if DK_HAVE_DB_H
      back = dksdbi_bdb_sync(p);
#endif
    } break;
    case DK_SDBI_TYPE_NDBM: {
#if DK_HAVE_NDBM_H
      back = dksdbi_ndbm_sync(p);
#endif
    } break;
  }
  }
  
  return back;
}



int
dksdbi_sync DK_P1(dk_sdbi_t,p)
{
  int back = 0;
  
  if(p) { back = do_sync((dk_sdbi_db *)p); }
  
  return back;
}

