/* -*- Mode: C -*-
  ======================================================================
  FILE: icalbdbset.c
 ======================================================================*/


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "icalbdbset.h"
#include "icalgauge.h"
#include <errno.h>
#include <sys/stat.h> /* for stat */
#include <stdio.h>

#ifndef WIN32
#include <unistd.h> /* for stat, getpid, unlink */
#include <fcntl.h> /* for fcntl */
#else
#define	S_IRUSR	S_IREAD		/* R for owner */
#define	S_IWUSR	S_IWRITE	/* W for owner */
#endif
#include <stdlib.h>
#include <string.h>

#include "icalbdbsetimpl.h"

#define STRBUF_LEN 255
#define MAX_RETRY 5

extern int errno;



/* these are just stub functions */
icalerrorenum icalbdbset_read_database(icalbdbset* bset, char *(*pfunc)(const DBT *dbt));
icalerrorenum icalbdbset_create_cluster(const char *path);
int icalbdbset_cget(DBC *dbcp, DBT *key, DBT *data, int access_method);

static int _compare_keys(DB *dbp, const DBT *a, const DBT *b);

    
/** Default options used when NULL is passed to icalset_new() **/
icalbdbset_options icalbdbset_options_default = {ICALBDB_EVENTS, DB_BTREE, 0644, 0, NULL, NULL};


static DB_ENV *ICAL_DB_ENV = 0;

/** Initialize the db environment */

int icalbdbset_init_dbenv(char *db_env_dir, void (*logDbFunc)(const char*, char*)) {
  int ret;
  int flags;

  if (db_env_dir) {
    struct stat env_dir_sb;
    
    if (stat(db_env_dir, &env_dir_sb)) {
      fprintf(stderr, "The directory '%s' is missing, please create it.\n", db_env_dir);
      return EINVAL;
    }
  }
  
  ret = db_env_create(&ICAL_DB_ENV, 0);

  if (ret) {
    /* some kind of error... */
    return ret;
  }

  /* Do deadlock detection internally */
  if ((ret = ICAL_DB_ENV->set_lk_detect(ICAL_DB_ENV, DB_LOCK_DEFAULT)) != 0) {
    char * foo = db_strerror(ret);
    fprintf(stderr, "Could not initialize the database locking environment\n");
    return ret;
  }    

  flags = DB_INIT_LOCK | DB_INIT_TXN | DB_CREATE | DB_THREAD | \
    DB_RECOVER | DB_INIT_LOG | DB_INIT_MPOOL;
  ret = ICAL_DB_ENV->open(ICAL_DB_ENV, db_env_dir,  flags, S_IRUSR|S_IWUSR);
  
  if (ret) {
    char * foo = db_strerror(ret);
    ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "dbenv->open");
    return ret;
  }

  /* display additional error messages */
  if (logDbFunc != NULL) {
     ICAL_DB_ENV->set_errcall(ICAL_DB_ENV, logDbFunc);
  }

  return ret;
}

void icalbdbset_checkpoint(void)
{
  int ret;
  char *err;

  switch (ret = ICAL_DB_ENV->txn_checkpoint(ICAL_DB_ENV, 0,0,0)) {
  case 0:
  case DB_INCOMPLETE:
    break;
  default:
    err = db_strerror(ret);
    ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "checkpoint failed");
    abort();
  }
}

void icalbdbset_rmdbLog(void)
{
    int      ret = 0;
    char**   listp;

    /* remove log files that are archivable (ie. no longer needed) */
    if (ICAL_DB_ENV->log_archive(ICAL_DB_ENV, &listp, DB_ARCH_ABS) == 0) {
       if (listp != NULL) {
          int ii = 0;
          while (listp[ii] != NULL) {
              ret = unlink(listp[ii]);
              ii++;
          }
          free(listp);
       }
    }
}

int icalbdbset_cleanup(void)
{
  int ret = 0;

  /* one last checkpoint.. */
  icalbdbset_checkpoint();

    /* remove logs that are not needed anymore */
    icalbdbset_rmdbLog();

  if (ICAL_DB_ENV) 
     ret = ICAL_DB_ENV->close(ICAL_DB_ENV, 0);

  return ret;
}

DB_ENV *icalbdbset_get_env(void) {
  return ICAL_DB_ENV;
}


/** Initialize an icalbdbset.  Also attempts to populate from the
 *  database (primary if only dbp is given, secondary if sdbp is
 *  given) and creates an empty object if retrieval is unsuccessful.
 *  pfunc is used to unpack data from the database.  If not given, we
 *  assume data is a string.
 */

icalset* icalbdbset_init(icalset* set, const char* dsn, void* options_in)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalbdbset_options *options = options_in;
    int ret;
    DB *cal_db;
    char *subdb_name;

    if (options == NULL) 
      *options = icalbdbset_options_default;

    switch (options->subdb) {
    case ICALBDB_CALENDARS:
      subdb_name = "calendars";
      break;
    case ICALBDB_EVENTS:
      subdb_name = "events";
      break;
    case ICALBDB_TODOS:
      subdb_name = "todos";
      break;
    case ICALBDB_REMINDERS:
      subdb_name = "reminders";
      break;
    }
  
    cal_db = icalbdbset_bdb_open(set->dsn, 
				 subdb_name, 
				 options->dbtype, 
				 options->mode,
				 options->flag);
    if (cal_db == NULL)
      return NULL;

    bset->dbp = cal_db;
    bset->sdbp = NULL;
    bset->gauge = 0;
    bset->cluster = 0;
  
    if ((ret = icalbdbset_read_database(bset, options->pfunc)) != ICAL_NO_ERROR) {
      return NULL;
    }

    return (icalset *)bset;
}


/** open a database and return a reference to it.  Used only for
    opening the primary index.
    flag = set_flag() DUP | DUP_SORT
 */

icalset* icalbdbset_new(const char* database_filename, 
			icalbdbset_subdb_type subdb_type,
			int dbtype, int flag)
{
  icalbdbset_options options = icalbdbset_options_default;

  options.subdb = subdb_type;
  options.dbtype = dbtype;
  options.flag = flag;
  
  /* this will in turn call icalbdbset_init */
  return icalset_new(ICAL_BDB_SET, database_filename, &options);
}

/**
 *  Open a secondary database, used for accessing secondary indices.
 *  The callback function tells icalbdbset how to associate secondary
 *  key information with primary data.  See the BerkeleyDB reference
 *  guide for more information.
 */

DB * icalbdbset_bdb_open_secondary(DB *dbp,
			       const char *database,
			       const char *sub_database, 
			       int (*callback) (DB *db, 
						const DBT *dbt1, 
						const DBT *dbt2, 
						DBT *dbt3),
			       int type)
{
  int ret;
  int flags;
    DB  *sdbp = NULL;

  if (!sub_database) 
        return NULL;

  if (!ICAL_DB_ENV)
        icalbdbset_init_dbenv(NULL, NULL);

  /* Open/create secondary */
  if((ret = db_create(&sdbp, ICAL_DB_ENV, 0)) != 0) {
        ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "secondary index: %s", sub_database);
        return NULL;
  }

  if ((ret = sdbp->set_flags(sdbp, DB_DUP | DB_DUPSORT)) != 0) {
        ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "set_flags error for secondary index: %s", sub_database);
        return NULL;
  }

  flags = DB_CREATE  | DB_THREAD;
  if ((ret = sdbp->open(sdbp, database, sub_database, type, (u_int32_t) flags, 0644)) != 0) {
        ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "failed to open secondary index: %s", sub_database);
        if (ret == DB_RUNRECOVERY)
            abort();
        else
            return NULL;
  }

  /* Associate the primary index with a secondary */
  if((ret = dbp->associate(dbp, sdbp, callback, 0)) != 0) {
        ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "failed to associate secondary index: %s", sub_database);
        return NULL;
  }

  return sdbp;
}

DB* icalbdbset_bdb_open(const char* path, 
				  const char *subdb, 
				int dbtype, 
				mode_t mode,
				int flag)
{
    DB  *dbp = NULL;
  int ret;
  int flags;

  /* Initialize the correct set of db subsystems (see capdb.c) */
  flags =  DB_CREATE | DB_THREAD;

  /* should just abort here instead of opening an env in the current dir..  */
  if (!ICAL_DB_ENV)
        icalbdbset_init_dbenv(NULL, NULL);

  /* Create and initialize database object, open the database. */
  if ((ret = db_create(&dbp, ICAL_DB_ENV, 0)) != 0) {
        return (NULL);
  }

  /* set comparison function, if BTREE */
  if (dbtype == DB_BTREE)
    dbp->set_bt_compare(dbp, _compare_keys);

  /* set DUP, DUPSORT */
  if (flag != 0)
    dbp->set_flags(dbp, flag);

  if ((ret = dbp->open(dbp, path, subdb, dbtype, flags, mode)) != 0) {
        ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "%s (database: %s): open failed.", path, subdb);
        if (ret == DB_RUNRECOVERY)
    abort();
        else
            return NULL;
  }

  return (dbp);
}

    
/* icalbdbset_parse_data -- parses using pfunc to unpack data. */
char *icalbdbset_parse_data(DBT *dbt, char *(*pfunc)(const DBT *dbt)) 
{
  char *ret;

  if(pfunc) {
    ret = (char *)pfunc(dbt);
  } else {
    ret = (char *) dbt->data;
  }

  return (ret);
}

/* This populates a cluster with the entire contents of a database */
icalerrorenum icalbdbset_read_database(icalbdbset* bset, char *(*pfunc)(const DBT *dbt))
{

  DB *dbp;
  DBC *dbcp;
  DBT key, data;
  char *str, *szpstr;
  int ret;
  char keystore[256];
  char datastore[1024];
  char *more_mem = NULL;
  DB_TXN *tid;

  memset(&key, 0, sizeof(DBT));
  memset(&data, 0, sizeof(DBT));

  if (bset->sdbp) { dbp = bset->sdbp; }
  else { dbp = bset->dbp; }
     
  if(!dbp) { goto err1; }

  bset->cluster = icalcomponent_new(ICAL_XROOT_COMPONENT);

  if ((ret = ICAL_DB_ENV->txn_begin(ICAL_DB_ENV, NULL, &tid, 0)) != 0) {
	char *foo = db_strerror(ret);
	abort();
  }

  /* acquire a cursor for the database */
  if ((ret = dbp->cursor(dbp, tid, &dbcp, 0)) != 0) {
    dbp->err(dbp, ret, "primary index");
    goto err1;
  }

  key.flags = DB_DBT_USERMEM;
  key.data = keystore;
  key.ulen = sizeof(keystore);

  data.flags= DB_DBT_USERMEM;
  data.data = datastore;
  data.ulen = sizeof(datastore);


  /* fetch the key/data pair */
  while (1) {
    ret = dbcp->c_get(dbcp, &key, &data, DB_NEXT);
    if (ret == DB_NOTFOUND) {
      break;
    } else if (ret == ENOMEM) {
      if (more_mem) free (more_mem);
      more_mem = malloc(data.ulen+1024);
      data.data = more_mem;
      data.ulen = data.ulen+1024;
    } else if (ret == DB_LOCK_DEADLOCK) {
      char *foo = db_strerror(ret);
      abort(); /* should retry in case of DB_LOCK_DEADLOCK */
    } else if (ret) {
      char *foo = db_strerror(ret);
      /* some other weird-ass error  */
      dbp->err(dbp, ret, "cursor");
      abort();
    } else {
      icalcomponent *cl;
      
      /* this prevents an array read bounds error */
      if((str = (char *)calloc(data.size + 1, sizeof(char)))==NULL)
	goto err2;
      memcpy(str, (char *)data.data, data.size);
      
      cl = icalparser_parse_string(str);
      
      icalcomponent_add_component(bset->cluster, cl);
      free(str);
    }
  }
  if(ret != DB_NOTFOUND) {
      goto err2;
  }


  if (more_mem) {
      free(more_mem);
      more_mem = NULL;
  }

  if ((ret = dbcp->c_close(dbcp)) != 0) {
        char * foo = db_strerror(ret);
        abort(); /* should retry in case of DB_LOCK_DEADLOCK */
  }

    if ((ret = tid->commit(tid, 0)) != 0) {
        char * foo = db_strerror(ret);
        abort();
    }

  return ICAL_NO_ERROR;

 err2:
  if (more_mem) free(more_mem);
  dbcp->c_close(dbcp);
  abort(); /* should retry in case of DB_LOCK_DEADLOCK */
  return ICAL_INTERNAL_ERROR;

 err1:
  dbp->err(dbp, ret, "cursor index");
  abort();
  return (ICAL_FILE_ERROR);
}


/* XXX add more to this */
void icalbdbset_free(icalset* set)
{
    icalbdbset *bset = (icalbdbset*)set;
    int ret;

    icalerror_check_arg_rv((bset!=0),"bset");

    if (bset->cluster != 0){
	icalbdbset_commit(set);
	icalcomponent_free(bset->cluster);
	bset->cluster=0;
    }

    if(bset->gauge !=0){
	icalgauge_free(bset->gauge);
    }

    if(bset->path != 0){
	free((char *)bset->path);
	bset->path = 0;
    }

    if(bset->sindex != 0) {
      free((char *)bset->sindex);
      bset->sindex = 0;
    }

    if (bset->dbp && 
	((ret = bset->dbp->close(bset->dbp, 0)) != 0)) {
    }
    bset->dbp = NULL;
}

/* return cursor is in rdbcp */
int icalbdbset_acquire_cursor(DB *dbp, DB_TXN *tid, DBC **rdbcp) {
  int ret=0;

  if((ret = dbp->cursor(dbp, tid, rdbcp, 0)) != 0) {
    dbp->err(dbp, ret, "couldn't open cursor");
    goto err1;
  }

  return ICAL_NO_ERROR;

 err1:
  return ICAL_FILE_ERROR;

}

/* returns key/data in arguments */
int icalbdbset_get_first(DBC *dbcp, DBT *key, DBT *data) {
  return icalbdbset_cget(dbcp, key, data, DB_FIRST);
}

int icalbdbset_get_next(DBC *dbcp, DBT *key, DBT *data) {
  return icalbdbset_cget(dbcp, key, data, DB_NEXT);
}

int icalbdbset_get_last(DBC *dbcp, DBT *key, DBT *data) {
  return icalbdbset_cget(dbcp, key, data, DB_LAST);
}

int icalbdbset_get_key(DBC *dbcp, DBT *key, DBT *data) {
  return icalbdbset_cget(dbcp, key, data, DB_SET);
}

int icalbdbset_delete(DB *dbp, DBT *key) {
  DB_TXN *tid;
  int ret;
    int done = 0;
    int retry = 0;

    while ((retry < MAX_RETRY) && !done) {

  if ((ret = ICAL_DB_ENV->txn_begin(ICAL_DB_ENV, NULL, &tid, 0)) != 0) {
            if (ret == DB_LOCK_DEADLOCK) {
                retry++;
                continue;
            }
            else {
	char *foo = db_strerror(ret);
	abort();
  }
        }

        if ((ret = dbp->del(dbp, tid, key, 0)) != 0) {
            if (ret == DB_NOTFOUND) {
                /* do nothing - not an error condition */
            }
            else if (ret == DB_LOCK_DEADLOCK) {
                tid->abort(tid);
                retry++;
                continue;
            }
            else {
                char *strError = db_strerror(ret);
                icalerror_warn("icalbdbset_delete faild: ");
                icalerror_warn(strError);
                tid->abort(tid);
                return ICAL_FILE_ERROR;
            }
  }

  if ((ret = tid->commit(tid, 0)) != 0) {
            if (ret == DB_LOCK_DEADLOCK) {
                tid->abort(tid);
                retry++;
                continue;
            }
            else {
        char * foo = db_strerror(ret);
        abort();
  }
       }

       done = 1;   /* all is well */
    }

    if (!done) {
        if (tid != NULL) tid->abort(tid);
    }

  return ret;
}

int icalbdbset_cget(DBC *dbcp, DBT *key, DBT *data, int access_method) {
  int ret=0;

  key->flags |= DB_DBT_MALLOC; /* change these to DB_DBT_USERMEM */
  data->flags |= DB_DBT_MALLOC;

  /* fetch the key/data pair */
  if((ret = dbcp->c_get(dbcp, key, data, access_method)) != 0) {
    goto err1;
  }

  return ICAL_NO_ERROR;

 err1:
  return ICAL_FILE_ERROR;
}


int icalbdbset_cput(DBC *dbcp, DBT *key, DBT *data, int access_method) {
  int ret=0;

  key->flags |= DB_DBT_MALLOC; /* change these to DB_DBT_USERMEM */
  data->flags |= DB_DBT_MALLOC;

  /* fetch the key/data pair */
  if((ret = dbcp->c_put(dbcp, key, data, 0)) != 0) {
    goto err1;
  }

  return ICAL_NO_ERROR;

 err1:
  return ICAL_FILE_ERROR;
}


int icalbdbset_put(DB *dbp, DBT *key, DBT *data, int access_method)
{
    int     ret   = 0;
    DB_TXN *tid   = NULL;
    int     retry = 0;
    int     done  = 0;

    while ((retry < MAX_RETRY) && !done) {

  if ((ret = ICAL_DB_ENV->txn_begin(ICAL_DB_ENV, NULL, &tid, 0)) != 0) {
            if (ret == DB_LOCK_DEADLOCK) {
                retry++;
                continue;
            }
            else {
	char *foo = db_strerror(ret);
	abort();
  }
        }
  
        if ((ret = dbp->put(dbp, tid, key, data, access_method)) != 0) {
            if (ret == DB_LOCK_DEADLOCK) {
                tid->abort(tid);
                retry++;
                continue;
            }
            else {
    char *strError = db_strerror(ret);
    icalerror_warn("icalbdbset_put faild: ");
    icalerror_warn(strError);
    tid->abort(tid);
    return ICAL_FILE_ERROR;
  }
        }

  if ((ret = tid->commit(tid, 0)) != 0) {
            if (ret == DB_LOCK_DEADLOCK) {
                tid->abort(tid);
                retry++;
                continue;
            }
            else {
        char * foo = db_strerror(ret);
        abort();
  }
       }

       done = 1;   /* all is well */
    }

    if (!done) {
        if (tid != NULL) tid->abort(tid);
        return ICAL_FILE_ERROR;
    }
    else
  return ICAL_NO_ERROR;
}

int icalbdbset_get(DB *dbp, DB_TXN *tid, DBT *key, DBT *data, int flags)
{
    return (dbp->get(dbp, tid, key, data, flags));
}

/** Return the path of the database file **/

const char* icalbdbset_path(icalset* set)
{
    icalerror_check_arg_rz((set!=0),"set");

    return set->dsn;
}

const char* icalbdbset_subdb(icalset* set)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalerror_check_arg_rz((bset!=0),"bset");

    return bset->subdb;
}


/** Write changes out to the database file.
 */

icalerrorenum icalbdbset_commit(icalset *set) {
  DB *dbp;
  DBC *dbcp;
  DBT key, data;
  icalcomponent *c;
  char *str;
  int ret=0;
    int           reterr = ICAL_NO_ERROR;
  char keystore[256];
    char          uidbuf[256];
  char datastore[1024];
  char *more_mem = NULL;
    DB_TXN        *tid = NULL;
  icalbdbset *bset = (icalbdbset*)set;
  int bad_uid_counter = 0;
    int           retry = 0, done = 0, completed = 0, deadlocked = 0;

  icalerror_check_arg_re((bset!=0),"bset",ICAL_BADARG_ERROR);  

  dbp = bset->dbp;
  icalerror_check_arg_re((dbp!=0),"dbp is invalid",ICAL_BADARG_ERROR);

  if (bset->changed == 0)
    return ICAL_NO_ERROR;

  memset(&key, 0, sizeof(key));
  memset(&data, 0, sizeof(data));

  key.flags = DB_DBT_USERMEM; 
  key.data = keystore;
  key.ulen = sizeof(keystore);

  data.flags = DB_DBT_USERMEM;
  data.data = datastore;
  data.ulen = sizeof(datastore);
  
  if (!ICAL_DB_ENV)
        icalbdbset_init_dbenv(NULL, NULL);

    while ((retry < MAX_RETRY) && !done) {

  if ((ret = ICAL_DB_ENV->txn_begin(ICAL_DB_ENV, NULL, &tid, 0)) != 0) {
            if (ret ==  DB_LOCK_DEADLOCK) {
                retry++;
                continue;
            }
            else if (ret == DB_RUNRECOVERY) {
                ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "icalbdbset_commit: txn_begin failed");
	abort();
  }
            else {
                ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "icalbdbset_commit");
                return ICAL_INTERNAL_ERROR;
            }
        }

  /* first delete everything in the database, because there could be removed components */
  if ((ret = dbp->cursor(dbp, tid, &dbcp, DB_DIRTY_READ)) != 0) {
            tid->abort(tid);
            if (ret == DB_LOCK_DEADLOCK) {
                retry++;
                continue;
            }
            else if (ret == DB_RUNRECOVERY) {
                ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "curor failed");
    abort();
            }
            else {
                ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "curor failed");
    /* leave bset->changed set to true */
    return ICAL_INTERNAL_ERROR;
  }
        }

  /* fetch the key/data pair, then delete it */
        completed = 0;
        while (!completed && !deadlocked) {
    ret = dbcp->c_get(dbcp, &key, &data, DB_NEXT);
    if (ret == DB_NOTFOUND) {
                completed = 1;
    } else if (ret == ENOMEM)  {
      if (more_mem) free(more_mem);
      more_mem = malloc(data.ulen+1024);
      data.data = more_mem;
      data.ulen = data.ulen+1024;
    } else if (ret == DB_LOCK_DEADLOCK) {
                deadlocked = 1;
            } else if (ret == DB_RUNRECOVERY) {
                tid->abort(tid);
                ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "c_get failed.");
      abort();
            } else if (ret == 0) {
       if ((ret = dbcp->c_del(dbcp,0))!=0) {
         dbp->err(dbp, ret, "cursor");
         if (ret == DB_KEYEMPTY) {
	    /* never actually created, continue onward.. */
                        /* do nothing - break; */
         } else if (ret == DB_LOCK_DEADLOCK) {
                        deadlocked = 1;
         } else {
            char *foo = db_strerror(ret);
            abort();
         }
       }
            } else {  /* some other non-fatal error */
                dbcp->c_close(dbcp);
                tid->abort(tid);
                if (more_mem) {
                    free(more_mem);
                    more_mem = NULL;
                }
                return ICAL_INTERNAL_ERROR;
    }
  }

  if (more_mem) {
	  free(more_mem);
	  more_mem = NULL;
  }

        if (deadlocked) {
            dbcp->c_close(dbcp);
            tid->abort(tid);
            retry++;
            continue;  /* next retry */
        }

        deadlocked = 0;
        for (c = icalcomponent_get_first_component(bset->cluster,ICAL_ANY_COMPONENT);
             c != 0 && !deadlocked;
      c = icalcomponent_get_next_component(bset->cluster,ICAL_ANY_COMPONENT)) {

            memset(&key, 0, sizeof(key));
            memset(&data, 0, sizeof(data));

    /* Note that we're always inserting into a primary index. */
    if (icalcomponent_isa(c) != ICAL_VAGENDA_COMPONENT)  {
      char *uidstr = (char *)icalcomponent_get_uid(c);
                if (!uidstr) {   /* this shouldn't happen */
                    /* no uid string, we need to add one */
                    snprintf(uidbuf, 256, "baduid%d-%d", getpid(), bad_uid_counter++);
                    key.data = uidbuf;
      } else {
                    key.data = uidstr;
      }
    } else {
      char *relcalid = NULL;
      relcalid = (char*)icalcomponent_get_relcalid(c);
      if (relcalid == NULL) {
                    snprintf(uidbuf, 256, "baduid%d-%d", getpid(), bad_uid_counter++);
                    key.data = uidbuf;
      } else {
                    key.data = relcalid;
      }
    }
    key.size = strlen(key.data);

    str = icalcomponent_as_ical_string_r(c);
    data.data = str;
    data.size = strlen(str);

    if ((ret = dbcp->c_put(dbcp, &key, &data, DB_KEYLAST)) != 0) {
                if (ret == DB_LOCK_DEADLOCK) {
                    deadlocked = 1;
                }
                else if (ret == DB_RUNRECOVERY) {
                    ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "c_put failed.");
                    abort();
                }
                else {
                    ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "c_put failed %s.", str);
                    /* continue to try to put as many icalcomponent as possible */
                    reterr = ICAL_INTERNAL_ERROR;
                }
            }
    }

    free(str);

        if (deadlocked) {
            dbcp->c_close(dbcp);
            tid->abort(tid);
            retry++;
            continue;
  } 

  if ((ret = dbcp->c_close(dbcp)) != 0) {
            tid->abort(tid);
            if (ret == DB_LOCK_DEADLOCK) {
                retry++;
                continue;
            }
            else if (ret == DB_RUNRECOVERY) {
                ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "c_closed failed.");
                abort();
            }
            else {
                ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "c_closed failed.");
                reterr = ICAL_INTERNAL_ERROR;
            }
  }

  if ((ret = tid->commit(tid, 0)) != 0) {
            tid->abort(tid);
            if (ret == DB_LOCK_DEADLOCK) {
                retry++;
                continue;
            }
            else if (ret == DB_RUNRECOVERY) {
                ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "commit failed.");
    abort();
  }
            else {
                ICAL_DB_ENV->err(ICAL_DB_ENV, ret, "commit failed.");
                reterr = ICAL_INTERNAL_ERROR;
            }
        }

        done = 1;
    }

  bset->changed = 0;    
    return reterr;
} 


void icalbdbset_mark(icalset* set)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalerror_check_arg_rv((bset!=0),"bset");

    bset->changed = 1;
}


icalcomponent* icalbdbset_get_component(icalset* set)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalerror_check_arg_rz((bset!=0),"bset");

    return bset->cluster;
}


/* manipulate the components in the cluster */

icalerrorenum icalbdbset_add_component(icalset *set,
					icalcomponent* child)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalerror_check_arg_re((bset!=0),"bset", ICAL_BADARG_ERROR);
    icalerror_check_arg_re((child!=0),"child",ICAL_BADARG_ERROR);

    icalcomponent_add_component(bset->cluster,child);

    icalbdbset_mark(set);

    return ICAL_NO_ERROR;
}


icalerrorenum icalbdbset_remove_component(icalset *set,
					   icalcomponent* child)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalerror_check_arg_re((bset!=0),"bset", ICAL_BADARG_ERROR);
    icalerror_check_arg_re((child!=0),"child",ICAL_BADARG_ERROR);

    icalcomponent_remove_component(bset->cluster,child);

    icalbdbset_mark(set);

    return ICAL_NO_ERROR;
}


int icalbdbset_count_components(icalset *set,
				 icalcomponent_kind kind)
{
    icalbdbset *bset = (icalbdbset*)set;

    if(set == 0){
	icalerror_set_errno(ICAL_BADARG_ERROR);
	return -1;
    }

    return icalcomponent_count_components(bset->cluster,kind);
}


/** Set the gauge **/

icalerrorenum icalbdbset_select(icalset* set, icalgauge* gauge)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalerror_check_arg_re((bset!=0),"bset", ICAL_BADARG_ERROR);
    icalerror_check_arg_re(gauge!=0,"gauge",ICAL_BADARG_ERROR);

    bset->gauge = gauge;

    return ICAL_NO_ERROR;
}

    
/** Clear the gauge **/

void icalbdbset_clear(icalset* set)
{
  icalbdbset *bset = (icalbdbset*)set;
  icalerror_check_arg_rv((bset!=0),"bset");
    
  bset->gauge = 0;
}


icalcomponent* icalbdbset_fetch(icalset* set, icalcomponent_kind kind, const char* uid)
{
    icalcompiter i;    
  icalbdbset *bset = (icalbdbset*)set;
  icalerror_check_arg_rz((bset!=0),"bset");
    
  for(i = icalcomponent_begin_component(bset->cluster, kind);
	icalcompiter_deref(&i)!= 0; icalcompiter_next(&i)){
	
	icalcomponent *this = icalcompiter_deref(&i);
	icalproperty  *p = NULL;
	const char    *this_uid = NULL;

	if (this != 0){
	    if (kind == ICAL_VAGENDA_COMPONENT) {
		p = icalcomponent_get_first_property(this,ICAL_RELCALID_PROPERTY);
		if (p != NULL) this_uid = icalproperty_get_relcalid(p);
	    } else {
		p = icalcomponent_get_first_property(this,ICAL_UID_PROPERTY);
		if (p != NULL) this_uid = icalproperty_get_uid(p);
	    }

	    if(this_uid==NULL){
		icalerror_warn("icalbdbset_fetch found a component with no UID");
		continue;
	    }

	    if (strcmp(uid,this_uid)==0){
		return this;
	    }
	}
    }

    return 0;
}


int icalbdbset_has_uid(icalset* store,const char* uid)
{
    assert(0); /* HACK, not implemented */
    return 0;
}


/******* support routines for icalbdbset_fetch_match *********/

struct icalbdbset_id {
    char* uid;
    char* recurrence_id;
    int sequence;
};

void icalbdbset_id_free(struct icalbdbset_id *id)
{
    if(id->recurrence_id != 0){
	free(id->recurrence_id);
    }

    if(id->uid != 0){
	free(id->uid);
    }

}

struct icalbdbset_id icalbdbset_get_id(icalcomponent* comp)
{

    icalcomponent *inner;
    struct icalbdbset_id id;
    icalproperty *p;

    inner = icalcomponent_get_first_real_component(comp);
    
    p = icalcomponent_get_first_property(inner, ICAL_UID_PROPERTY);

    assert(p!= 0);

    id.uid = strdup(icalproperty_get_uid(p));

    p = icalcomponent_get_first_property(inner, ICAL_SEQUENCE_PROPERTY);

    if(p == 0) {
	id.sequence = 0;
    } else { 
	id.sequence = icalproperty_get_sequence(p);
    }

    p = icalcomponent_get_first_property(inner, ICAL_RECURRENCEID_PROPERTY);

    if (p == 0){
	id.recurrence_id = 0;
    } else {
	icalvalue *v;
	v = icalproperty_get_value(p);
	id.recurrence_id = icalvalue_as_ical_string_r(v);

	assert(id.recurrence_id != 0);
    }

    return id;
}

/* Find the component that is related to the given
   component. Currently, it just matches based on UID and
   RECURRENCE-ID */

icalcomponent* icalbdbset_fetch_match(icalset* set, icalcomponent *comp)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalcompiter i;    
    struct icalbdbset_id comp_id, match_id;
    
    icalerror_check_arg_rz((bset!=0),"bset");
    comp_id = icalbdbset_get_id(comp);

    for(i = icalcomponent_begin_component(bset->cluster,ICAL_ANY_COMPONENT);
	icalcompiter_deref(&i)!= 0; icalcompiter_next(&i)){
	
	icalcomponent *match = icalcompiter_deref(&i);

	match_id = icalbdbset_get_id(match);

	if(strcmp(comp_id.uid, match_id.uid) == 0 &&
	   ( comp_id.recurrence_id ==0 || 
	     strcmp(comp_id.recurrence_id, match_id.recurrence_id) ==0 )){

	    /* HACK. What to do with SEQUENCE? */

	    icalbdbset_id_free(&match_id);
	    icalbdbset_id_free(&comp_id);
	    return match;
	    
	}
	
	icalbdbset_id_free(&match_id);
    }

    icalbdbset_id_free(&comp_id);
    return 0;

}


icalerrorenum icalbdbset_modify(icalset* set, icalcomponent *old,
				 icalcomponent *newc)
{
    assert(0); /* HACK, not implemented */
    return ICAL_NO_ERROR;
}

/* caller is responsible to cal icalbdbset_free_cluster first */
icalerrorenum  icalbdbset_set_cluster(icalset* set, icalcomponent* cluster)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalerror_check_arg_rz((bset!=0),"bset"); 

    bset->cluster = cluster;
}

icalerrorenum  icalbdbset_free_cluster(icalset* set)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalerror_check_arg_rz((bset!=0),"bset");

    if (bset->cluster != NULL) icalcomponent_free(bset->cluster);
}

icalcomponent* icalbdbset_get_cluster(icalset* set)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalerror_check_arg_rz((bset!=0),"bset");

    return (bset->cluster);
}


/** Iterate through components. */
icalcomponent* icalbdbset_get_current_component (icalset* set)
{
    icalbdbset *bset = (icalbdbset*)set;

    icalerror_check_arg_rz((bset!=0),"bset");

    return icalcomponent_get_current_component(bset->cluster);
}


icalcomponent* icalbdbset_get_first_component(icalset* set)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalcomponent *c=0;

    icalerror_check_arg_rz((bset!=0),"bset");

    do {
        if (c == 0)
	    c = icalcomponent_get_first_component(bset->cluster,
						  ICAL_ANY_COMPONENT);
        else
	    c = icalcomponent_get_next_component(bset->cluster,
						 ICAL_ANY_COMPONENT);

	     if(c != 0 && (bset->gauge == 0 ||
		      icalgauge_compare(bset->gauge,c) == 1)){
	    return c;
	}

      } while (c!=0);

    return 0;
}


icalsetiter icalbdbset_begin_component(icalset* set, icalcomponent_kind kind, icalgauge* gauge, const char* tzid)
{
    icalsetiter itr = icalsetiter_null;
    icalcomponent* comp = NULL;
    icalcompiter citr;
    icalbdbset *bset = (icalbdbset*) set;
    struct icaltimetype start, next, end;
    icalproperty *dtstart, *rrule, *prop, *due;
    struct icalrecurrencetype recur;
    icaltimezone *u_zone;
    int g = 0;
    int orig_time_was_utc = 0;

    icalerror_check_arg_re((set!=0), "set", icalsetiter_null);

    itr.gauge = gauge;
    itr.tzid = tzid;

    citr = icalcomponent_begin_component(bset->cluster, kind);
    comp = icalcompiter_deref(&citr);

    if (gauge == 0) {
        itr.iter = citr;
        return itr;
    }

    /* if there is a gauge, the first matched component is returned */
    while (comp != 0) {

        /* check if it is a recurring component and with guage expand, if so
         * we need to add recurrence-id property to the given component */
        rrule = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY);
        g = icalgauge_get_expand(gauge);

        if (rrule != 0
            && g == 1) {

            /* it is a recurring event */

            u_zone = icaltimezone_get_builtin_timezone(itr.tzid);

      	    /* use UTC, if that's all we have. */
            if (!u_zone)
                u_zone = icaltimezone_get_utc_timezone();


            recur = icalproperty_get_rrule(rrule);

            if (icalcomponent_isa(comp) == ICAL_VEVENT_COMPONENT) {
                dtstart = icalcomponent_get_first_property(comp, ICAL_DTSTART_PROPERTY);
                if (dtstart)
                    start = icalproperty_get_dtstart(dtstart);
            } else if (icalcomponent_isa(comp) == ICAL_VTODO_COMPONENT) {
                    due = icalcomponent_get_first_property(comp, ICAL_DUE_PROPERTY);
                    if (due)
                        start = icalproperty_get_due(due);
            }

            /* Convert to the user's timezone in order to be able to compare
             * the results from the rrule iterator. */
            if (icaltime_is_utc(start)) {
	        start = icaltime_convert_to_zone(start, u_zone);
                orig_time_was_utc = 1;
            }

            if (itr.last_component == NULL) {
                itr.ritr = icalrecur_iterator_new(recur, start);
                next = icalrecur_iterator_next(itr.ritr);
                itr.last_component = comp;
            }
            else {
                next = icalrecur_iterator_next(itr.ritr);
                if (icaltime_is_null_time(next)){
                    itr.last_component = NULL;
                    icalrecur_iterator_free(itr.ritr);
                    itr.ritr = NULL;
                    /* no matched occurence */
                    goto getNextComp;
                } else {
                    itr.last_component = comp;
                }
            }

            /* if it is excluded, do next one */
            if (icalproperty_recurrence_is_excluded(comp, &start, &next)) {
	        icalrecur_iterator_decrement_count(itr.ritr);
                continue;
            }

            /* add recurrence-id value to the property if the property already exist;
             * add the recurrence id property and the value if the property does not exist */
            prop = icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY);
            if (prop == 0)
                icalcomponent_add_property(comp, icalproperty_new_recurrenceid(next));
            else
                icalproperty_set_recurrenceid(prop, next);

            /* convert the next recurrence time into the user's timezone */
            if (orig_time_was_utc) 
                next = icaltime_convert_to_zone(next, icaltimezone_get_utc_timezone());

        } /* end of a recurring event */

        if (gauge == 0 || icalgauge_compare(itr.gauge, comp) == 1) {
	    /* find a matched and return it */
            itr.iter = citr;
            return itr;
        }
       
        /* if it is a recurring but no matched occurrence has been found OR
         * it is not a recurring and no matched component has been found,
         * read the next component to find out */
getNextComp: 
        if ((rrule != NULL && itr.last_component == NULL) ||
           (rrule == NULL)) {
            comp =  icalcompiter_next(&citr);
            comp = icalcompiter_deref(&citr);
    }
    } /* while */

    /* no matched component has found */
    return icalsetiter_null;
}

icalcomponent* icalbdbset_form_a_matched_recurrence_component(icalsetiter* itr)
{
    icalcomponent* comp = NULL;
    struct icaltimetype start, next, end;
    icalproperty *dtstart, *rrule, *prop, *due;
    struct icalrecurrencetype recur;
    icaltimezone *u_zone;
    int g = 0;
    int orig_time_was_utc = 0;

    comp = itr->last_component;

    if (comp == NULL || itr->gauge == NULL) {
        return NULL;
    }


    rrule = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY);
    /* if there is no RRULE, simply return to the caller */
    if (rrule == NULL)
        return NULL;

    u_zone = icaltimezone_get_builtin_timezone(itr->tzid);

    /* use UTC, if that's all we have. */
    if (!u_zone)
        u_zone = icaltimezone_get_utc_timezone();

    recur = icalproperty_get_rrule(rrule);

    if (icalcomponent_isa(comp) == ICAL_VEVENT_COMPONENT) {
        dtstart = icalcomponent_get_first_property(comp, ICAL_DTSTART_PROPERTY);
        if (dtstart)
            start = icalproperty_get_dtstart(dtstart);
    } else if (icalcomponent_isa(comp) == ICAL_VTODO_COMPONENT) {
        due = icalcomponent_get_first_property(comp, ICAL_DUE_PROPERTY);
        if (due)
            start = icalproperty_get_due(due);
    }

    /* Convert to the user's timezone in order to be able to compare the results
     * from the rrule iterator. */
    if (icaltime_is_utc(start)) {
        start = icaltime_convert_to_zone(start, u_zone);
        orig_time_was_utc = 1;
    }

    if (itr->ritr == NULL) {
        itr->ritr = icalrecur_iterator_new(recur, start);
        next = icalrecur_iterator_next(itr->ritr);
        itr->last_component = comp;
    } else {
        next = icalrecur_iterator_next(itr->ritr);
        if (icaltime_is_null_time(next)){
            /* no more recurrence, returns */
            itr->last_component = NULL;
            icalrecur_iterator_free(itr->ritr);
            itr->ritr = NULL;
            /* no more pending matched occurence,
             * all the pending matched occurences have been returned */
            return NULL;
        } else {
            itr->last_component = comp;
        }
    }

    /* if it is excluded, return NULL to the caller */
    if (icalproperty_recurrence_is_excluded(comp, &start, &next)) {
        icalrecur_iterator_decrement_count(itr->ritr);
       return NULL; 
    }

    /* set recurrence-id value to the property if the property already exist;
     * add the recurrence id property and the value if the property does not exist */
    prop = icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY);
    if (prop == 0)
        icalcomponent_add_property(comp, icalproperty_new_recurrenceid(next));
    else
        icalproperty_set_recurrenceid(prop, next);

    if (orig_time_was_utc) {
        next = icaltime_convert_to_zone(next, icaltimezone_get_utc_timezone());
    }


     if (itr->gauge == 0 || icalgauge_compare(itr->gauge, comp) == 1) {
         /* find a matched and return it */
         return comp;
     } 

     /* not matched */
     return NULL;

}

icalcomponent* icalbdbsetiter_to_next(icalset *set, icalsetiter* i)
{

    icalcomponent* comp = NULL;
    icalbdbset *bset = (icalbdbset*) set;
    struct icaltimetype start, next, end;
    icalproperty *dtstart, *rrule, *prop, *due;
    struct icalrecurrencetype recur;
    icaltimezone *u_zone;
    int g = 0;
    int orig_time_was_utc = 0;

    do {

        /* no pending occurence, read the next component */
        if (i->last_component == NULL) {
            comp = icalcompiter_next(&(i->iter));
        }
        else {
            comp = i->last_component;
        }

        /* no next component, simply return */
        if (comp == 0) return NULL;
        if (i->gauge == 0) return comp;

        /* finding the next matched component and return it to the caller */

        rrule = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY);
        g = icalgauge_get_expand(i->gauge);

        /* a recurring component with expand query */
        if (rrule != 0
            && g == 1) {

            u_zone = icaltimezone_get_builtin_timezone(i->tzid);

            /* use UTC, if that's all we have. */
            if (!u_zone)
                u_zone = icaltimezone_get_utc_timezone();

            recur = icalproperty_get_rrule(rrule);

            if (icalcomponent_isa(comp) == ICAL_VEVENT_COMPONENT) {
                dtstart = icalcomponent_get_first_property(comp, ICAL_DTSTART_PROPERTY);
                if (dtstart)
                    start = icalproperty_get_dtstart(dtstart);
            } else if (icalcomponent_isa(comp) == ICAL_VTODO_COMPONENT) {
                due = icalcomponent_get_first_property(comp, ICAL_DUE_PROPERTY);
                if (due)
                    start = icalproperty_get_due(due);
            }

            /* Convert to the user's timezone in order to be able to compare
             * the results from the rrule iterator. */
            if (icaltime_is_utc(start)) {
                start = icaltime_convert_to_zone(start, u_zone);
                orig_time_was_utc = 1;
            }

            if (i->ritr == NULL) {
                i->ritr = icalrecur_iterator_new(recur, start);
                next = icalrecur_iterator_next(i->ritr);
                i->last_component = comp;
            } else {
                next = icalrecur_iterator_next(i->ritr);
                if (icaltime_is_null_time(next)) {
                    i->last_component = NULL;
                    icalrecur_iterator_free(i->ritr);
                    i->ritr = NULL;
                    /* no more occurence, should go to get next component */
                    continue;
                } else {
                    i->last_component = comp;
                }
            }

            /* if it is excluded, do next one */
            if (icalproperty_recurrence_is_excluded(comp, &start, &next)) {
                icalrecur_iterator_decrement_count(i->ritr);
                continue;
            }

            /* set recurrence-id value to the property if the property already exist;
             * add the recurrence id property and the value if the property does not exist */
            prop = icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY);
            if (prop == 0)
               icalcomponent_add_property(comp, icalproperty_new_recurrenceid(next));
            else
               icalproperty_set_recurrenceid(prop, next);

            if (orig_time_was_utc) {
                next = icaltime_convert_to_zone(next, icaltimezone_get_utc_timezone());
            }
 
        } /* end of recurring event with expand query */

        if(comp != 0 && (i->gauge == 0 ||
            icalgauge_compare(i->gauge, comp) == 1)){
           /* found a matched, return it */ 
            return comp;
        }
    } while (comp != 0);

    return 0;

}

icalcomponent* icalbdbset_get_next_component(icalset* set)
{
    icalbdbset *bset = (icalbdbset*)set;
    icalcomponent *c=0;

    struct icaltimetype start, next;
    icalproperty *dtstart, *rrule, *prop, *due;
    struct icalrecurrencetype recur;
    int g = 0;

    icalerror_check_arg_rz((bset!=0),"bset");
    
    do {
            c = icalcomponent_get_next_component(bset->cluster,
					     ICAL_ANY_COMPONENT);
                if(c != 0 && (bset->gauge == 0 ||
                    icalgauge_compare(bset->gauge,c) == 1)){
                    return c;
                }

    } while(c != 0);
    
    return 0;
}

int icalbdbset_begin_transaction(DB_TXN* parent_tid, DB_TXN** tid)
{
    return (ICAL_DB_ENV->txn_begin(ICAL_DB_ENV, parent_tid, tid, 0));
}

int icalbdbset_commit_transaction(DB_TXN* txnid)
{
    return (txnid->commit(txnid, 0));
}


static int _compare_keys(DB *dbp, const DBT *a, const DBT *b)
{
/* 
 * Returns: 
 * < 0 if a < b 
 * = 0 if a = b 
 * > 0 if a > b 
 */ 
	
    char*  ac = (char*)a->data;
    char*  bc = (char*)b->data;
    return (strncmp(ac, bc, a->size));
}



