/*
 * W-NEWS       A simple NEWS processing package.
 *		history.c - operations on history file
 *
 * Version:     @(#)history.c     4.50    03-Feb-1995
 *
 * Authors:     Fred N. van Kempen, <waltje@Infomagic.COM>
 *              Edvard Tuinder, <ed@ow.org>
 *		Miquel van Smoorenburg, <miquels@drinkel.ow.org>
 *              Copyright 1988,1989,1990,1991,1992,1993 MicroWalt Corporation
 *		Copyright 1994,1995 Cistron Software C.V.
 *
 *              This program is free software; you can redistribute it and/or
 *              modify it under the terms of the GNU General Public License
 *              as published by the Free Software Foundation; either version
 *              2 of the License, or (at your option) any later version.
 */
#include "libwnews.h"
#if HAVE_DBM
#  if HAVE_SDBM
#    include <sdbm.h>
#  else
#    include <ndbm.h>
#  endif
#endif


/*
 * The structure used for storing articles in the dbm file:
 * the "key" is the message id (without the trailing \0)
 * the "data" is a list of:
 *
 * char date[12];	: ASCII  + \0
 * char namelen[4];	: ASCII 3 digits - space
 * char name[namelen];	: ASCII + \0
 * char namelen[4];
 * char name[namelen];
 *
 */

/* See if dbm file is already open. */
#if HAVE_DBM
DBM *dbm_cache;
int open_cache;
#endif
static char histfile[128];

/* Initialize dbm files */
void hist_init(fname)
char *fname;
{
#if HAVE_DBM
  open_cache = 1;
  if (fname == NULL) histfile[0] = 0;
    else strcpy(histfile, fname);
#endif
}

/* Close dbm files. */
void hist_close()
{
#if HAVE_DBM
  if (dbm_cache && open_cache) dbm_close(dbm_cache);
  open_cache = 0;
  dbm_cache = NULL;
#endif
}

/* Add this article to the "history" file. */
void
hist_add(msgid, name, artno, t, dbmonly)
char *msgid;
char *name;
unsigned int artno;
time_t t;
int dbmonly;
{
#if HAVE_HIST
  FILE *fp;
  struct tm *mydate;
#if HAVE_DBM
  DBM *db;
  datum key, data;
  char *hp;
  int newsize;
  char total[128];
  int totlen;
  int free_dptr = 0;
  int mode = O_RDWR | O_CREAT;

  /* Create data to be stored. */
  sprintf(total, "%s/%u", name, artno);
  totlen = strlen(total) + 5;
  sprintf(total, "%03d %s/%u", totlen, name, artno);

  /* History file name and mode. */
  if (histfile[0] == 0) sprintf(histfile, "%s/%s", VARDIR, HISTFILE);

  /* This is really only for mkhist. */
  if (dbmonly) mode |= O_TRUNC;

  /* See if already opened. */
  if (open_cache && dbm_cache) db = dbm_cache;
    else if ((db = dbm_open(histfile, mode, 0644)) == (DBM *)NULL) {
	logmsg(LOGERR|LOGSYS,
		"cannot write dbase file %s\n", histfile);
	return;
  }
  if (open_cache && dbm_cache == NULL) dbm_cache = db;

  /* See if it's already there. */
  key.dptr = strdup(msgid);
  key.dsize = strlen(msgid) + 1;
  data = dbm_fetch(db, key);
  if (data.dptr == NULL) {
	/* Was empty. Fill in time. */
	data.dptr = malloc(12);
	data.dsize = 12;
	memset(data.dptr, 0, data.dsize);
	sprintf(data.dptr, "%lu", t);
	free_dptr = 1;
  }

  /* Add the new item to the database. */
  newsize = data.dsize + totlen;
  if ((hp = malloc(newsize)) == NULL) {
	logmsg(LOGERR, "hist_add: out of memory\n");
	if (!open_cache) dbm_close(db);
	return;
  }
  memcpy(hp, data.dptr, data.dsize);
  if (free_dptr) free(data.dptr);

  sprintf(hp + data.dsize, "%s", total);
  data.dsize = newsize;
  data.dptr = hp;

  /* Now store the new item. */
  if (dbm_store(db, key, data, DBM_REPLACE)) {
	/* This can not go wrong. Does it? */
	logmsg(LOGERR, "can't store `%s' in dbm history.\n", msgid);
	return;
  }

  free(data.dptr);
  free(key.dptr);
  if (!open_cache) dbm_close(db);
#endif /* HAVE_DBM */

  if (dbmonly) return;

  /* Now also add the stuff to the ASCII history file. */
  if (histfile[0] == 0) sprintf(histfile, "%s/%s", VARDIR, HISTFILE);

  if ((fp = fopen(histfile, "a")) == (FILE *)NULL) {
	logmsg(LOGERR, "cannot append(%s)\n", histfile);
	return;
  }
  mydate = localtime(&t);
  fprintf(fp, "%s\t%02d/%02d/%02d %02d:%02d\t%s/%u\n",
	msgid, mydate->tm_mon + 1, mydate->tm_mday, mydate->tm_year,
		mydate->tm_hour, mydate->tm_min, name, artno);
  (void) fclose(fp);
  
#endif /* HAVE_HIST */
}


/* Check if we already have this article. */
int
hist_chk(msgid)
char *msgid;
{
#if HAVE_HIST
#if HAVE_DBM
  /*
   * ET 16/06/93
   * Now this is real easy, and probably a bit
   * faster to if dealing with large history files.
   */
  DBM *db;
  datum key, data;

  if (histfile[0] == 0) sprintf(histfile, "%s/%s", VARDIR, HISTFILE);
  if (open_cache && dbm_cache) db = dbm_cache;
    else if ((db = dbm_open(histfile, O_RDWR, 0644)) == (DBM *)NULL) {
	logmsg(LOGERR|LOGSYS,
		"cannot read dbase file %s\n", histfile);
	return(0);
  }
  if (open_cache && dbm_cache == NULL) dbm_cache = db;

  key.dptr = strdup(msgid);
  key.dsize = strlen(msgid) + 1;
  data = dbm_fetch(db, key);
  free(key.dptr);
  if (!open_cache) dbm_close(db);

  /* Duplicate? */
  if (data.dptr != (char *)NULL) return(1);

#else /* !HAVE_DBM */
  char buff[512];
  FILE *fp;
  char *sp;

  sprintf(buff, "%s/%s", VARDIR, HISTFILE);
  if ((fp = fopen(buff, "r")) == (FILE *)NULL) {
	logmsg(LOGERR|LOGSYS, "cannot open(%s)\n", buff);
	return(0);
  }

  while(fgets(buff, 512, fp) != (char *)NULL) {
	if (buff[0] == '\n') continue;
	if ((sp = strchr(buff, '\t')) != (char *)NULL) *sp = '\0';
	if (! strcmp(buff, msgid)) {
		(void) fclose(fp);
		return(1);
	}
  }
  (void) fclose(fp);
#endif /* HAVE_DBM */
#endif /* HAVE_HIST */
  return(0);
}

/* Delete an article from the dbm history file. */
void
hist_del(msgid, name, artno)
char *msgid;
char *name;
unsigned int artno;
{
#if HAVE_DBM
  DBM *db;
  datum key, data;
  char *hp, *dp;
  int sz, size;
  char fname[128];

  if (histfile[0] == 0) sprintf(histfile, "%s/%s", VARDIR, HISTFILE);

  /* See if already opened. */
  if (open_cache && dbm_cache) db = dbm_cache;
    else if ((db = dbm_open(histfile, O_RDWR, 0644)) == (DBM *)NULL) {
	logmsg(LOGERR|LOGSYS,
		"cannot read/write dbase file %s\n", histfile);
	return;
  }
  if (open_cache && dbm_cache == NULL) dbm_cache = db;

  /* See if we can find it. */
  key.dptr = strdup(msgid);
  key.dsize = strlen(msgid) + 1;
  data = dbm_fetch(db, key);

  /* If not, return. */
  if (data.dptr == NULL) {
	if (!open_cache) dbm_close(db);
	free(key.dptr);
	return;
  }

  /* Delete this instance. */
  if ((hp = malloc(data.dsize)) == NULL) {
	logmsg(LOGERR, "hist_del: out of memory\n");
	if (!open_cache) dbm_close(db);
	free(key.dptr);
	return;
  }
  sprintf(fname, "%s/%u", name, artno);
  memcpy(hp, data.dptr, data.dsize);
  data.dptr = hp;
  dp = hp + 12;
  size = data.dsize - 12;

  /* Loop over all articles in this storage. */
  while(size > 0) {
	(void) sscanf(dp, "%d", &sz);
	if (strcmp(fname, dp + 4) == 0) {
		/* Shift rest of data down. */
		memcpy(dp, dp + sz, size - sz);
		data.dsize -= sz;
	} else {
		/* Skip over this one. */
		dp += sz;
	}
	/* Still any left? */
	size -= sz;
  }

  /* Deleted all? */
  if (data.dsize <= sizeof(time_t))
	dbm_delete(db, key);
  else
	dbm_store(db, key, data, DBM_REPLACE);

  /* That's all, folks. */
  free(data.dptr);
  free(key.dptr);
  if (!open_cache) dbm_close(db);
#endif /* HAVE_DBM */
}

#if HAVE_DBM
/* Print contents of one history record in ascii. */
void hist_print(msgid, data, len, fp)
char *msgid;
char *data;
int len;
FILE *fp;
{
  int size, sz;
  time_t t;
  struct tm *mydate;

  sscanf(data, "%lu", &t);
  size = len - 12;
  data += 12;
  mydate = localtime(&t);

  while(size > 0) {
	sscanf(data, "%d", &sz);
	fprintf(fp, "%s\t%02d/%02d/%02d %02d:%02d\t%s\n",
		msgid, mydate->tm_mon + 1, mydate->tm_mday, mydate->tm_year,
		mydate->tm_hour, mydate->tm_min, data + 4);
	data += sz;
	size -= sz;
  }
}
#endif

