/*--------------------------------------------------------------------*/
/*    h i s t o r y . c                                               */
/*                                                                    */
/*    News history file maintenance for UUPC/extended.                */
/*                                                                    */
/*    Written by Mike Lipsie                                          */
/*                                                                    */
/*    The history file                                                */
/*                                                                    */
/*    This file describes and implements the history file.            */
/*                                                                    */
/*    The history file is [newsdir]/history and is "added" to by      */
/*    rnews.  It is "pruned" by expire and is used by rnews (to       */
/*    check that the article has not arrived before), expire (to      */
/*    find all copies of the article), and rn (to mark as read all    */
/*    copies of an article.)                                          */
/*                                                                    */
/*    The history file is entirely ASCII.                             */
/*                                                                    */
/*    The first line is a code that identifies the version level      */
/*    (so that future versions can automatically upgrade.)  For       */
/*    version one that code is "ZIP1".                                */
/*                                                                    */
/*    Every line (except the version line) is the record of a         */
/*    single incoming article.  That line is exactly as described     */
/*    without added spaces or other punctuation.  The first field     */
/*    is the Message-ID including (or added if they don't exist),     */
/*    the "<" and ">", a space, the date received (dd/mm/yyyy with    */
/*    days and months being zero filled if necessary), a space,       */
/*    and the "destination" information.                              */
/*                                                                    */
/*    The destination information is the Newsgroups:  with a colon    */
/*    and the article number for each group added (article number     */
/*    zero is used for newsgroups not accepted.)                      */
/*                                                                    */
/*    For example, if article <123@pyramid> was received on 3 Jan     */
/*    1992 and was posted to ba.food, ba.transportation, and          */
/*    soc.singles and ended up in article 4334, 1234, and 56789       */
/*    (respectively), the record would be                             */
/*                                                                    */
/*    <123@pyramid> 03/01/1992                                        */
/*    ba.food:4334,ba.transportation:1234,soc.singles:56789           */
/*                                                                    */
/*    To speed access into the history file there is a parallel       */
/*    file history.ndx which contains information where each day's    */
/*    worth of history records begins.  The first line is a           */
/*    version number and must be the same as that of the history      */
/*    file.  All the other records are the 10 character date code,    */
/*    a space, and a ten digit location code (right justified,        */
/*    zero or blank filled.)  The location code is the byte           */
/*    displacement into the history file of the first record with     */
/*    that date code.                                                 */
/*--------------------------------------------------------------------*/


/*--------------------------------------------------------------------*/
/*                        System include files                        */
/*--------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>

/*--------------------------------------------------------------------*/
/*                    UUPC/extended include files                     */
/*--------------------------------------------------------------------*/

#include "lib.h"
#include "hlib.h"
#include "timestmp.h"
#include "active.h"
#include "history.h"

/*  These variables are used to improve the performance of checking
 *  for previous existance of an article.
 *
 *  The basic theory of how this feature works is that a file
 *  (history.dbm) is built with enough information (it is hoped)
 *  to eliminate 99% (well, most) of the non matching message IDs.
 *  A check is made when a match is made confiming that the match
 *  is real.  Since most articles are not "re-sends" (all if you
 *  have only one feed) this won't impose a large overhead.
 *
 *  The information that is used to eliminate unnecessary checks into
 *  the history file for duplicate messageIDs is the first ten
 *  characters of the message ID (not including the leading "<"),
 *  the number of characters in the full ID (including the "<" and ">"),
 *  a checksum of the full ID (also including the "<" and ">"), and
 *  the location in the history file of the full ID.
 *
 *  Additionally, an array of MAXbuf (currently 8) buffers of history.dbm
 *  are maintained in memory to avoid disk I/O.
 *
 */

int cur_buff = 0;
int cur_off  = 0;
#define MAXbuf 8

char *hb[MAXbuf];
int hb_blk[MAXbuf];

extern FILE *hfile;

FILE *hdbm_file = NULL;

struct perf_elem {
   char IDpart[10];  /* The first 10 characters (after the "<") of the message-ID */
   char cksum;       /* Checksum of the entire ID (including <>) */
   char IDlen;       /* Number of characters in the ID (including <>) */
   long where;       /* ftell() in file of actual record */
};


/*--------------------------------------------------------------------*/
/*    h i s t o r y _ e x i s t s                                     */
/*                                                                    */
/*    Determine if the history file exists and is a file (instead     */
/*    of a directory).                                                */
/*--------------------------------------------------------------------*/

boolean history_exists( void )
{
   char hfile_name[FILENAME_MAX];
   struct stat buff;

   mkfilename(hfile_name, E_newsdir, "history");

   if ((stat(hfile_name, &buff) == 0) && buff.st_mode & S_IFREG)
      return TRUE;
   else
      return FALSE;

} /* history_exists */


/*--------------------------------------------------------------------*/
/*    o p e n _ h i s t o r y                                         */
/*                                                                    */
/*    Open the history file and verify that it is of the correct      */
/*    version.  If any error occurs, NULL is returned.                */
/*--------------------------------------------------------------------*/

#ifdef __TURBOC__
#pragma argsused
#endif

FILE *open_history(char *history_date)
{

#ifdef NEXT_RELEASE_MAYBE
   char hfile_name[FILENAME_MAX];
   FILE *hfile;
   FILE *index_file;
   char buff[BUFSIZ];

   mkfilename(hfile_name, newsdir, "history");
   hfile = fopen(hfile_name, "a+b");
   if (hfile == NULL) {
      printmsg(0,"Unable to open history file");
      return hfile;
   }
   fseek(hfile, 0L, SEEK_SET);
   fgets(buff, sizeof(buff), hfile);
   buff[strlen(buff)-1] = '\0';
   if (buff[strlen(buff)-1] == '\r') buff[strlen(buff)-1] = '\0';
   if ((strlen(buff)) != strlen(H_VERSION)) {
      printmsg(0, "History version incorrect");
      return NULL;
   }
   if (!equal(buff, H_VERSION)) {
      printmsg(0, "History version incorrect");
      return NULL;
   }


   fseek(hfile, 0L, SEEK_END);      /* I know it is unnecessary */

   /* Now the index file */
   strcat(hfile_name, ".ndx");
   index_file = fopen(hfile_name, "a+b");
   if (index_file == NULL) {
      printmsg(0,"Unable to open history file index");
   }
   fseek(index_file, 0L, SEEK_SET);
   fgets(buff, sizeof(buff), index_file);
   buff[strlen(buff)-1] = '\0';
   if (buff[strlen(buff)-1] == '\r') buff[strlen(buff)-1] = '\0';
   if ((strlen(buff)) != strlen(H_VERSION)) {
      printmsg(0, "History index version incorrect");
   }
   if (!equal(buff, H_VERSION)) {
      printmsg(0, "History index version incorrect");
   }
   fseek(index_file, -21L, SEEK_END);
   fgets(buff, 25, index_file);
   if (strncmp(history_date, buff, strlen(history_date)) != 0) {
      fseek(index_file, 0L, SEEK_END);
      sprintf(buff, "%s %9.9ld\n", history_date, ftell(hfile));
      fwrite(buff, sizeof(char), strlen(buff), index_file);
   }
   fclose(index_file);
   #else
   hfile = NULL;
#endif

   return NULL;

}



/*
   Create a history file.
*/

FILE *create_history(char *history_date) {

char hfile_name[FILENAME_MAX];
FILE *hfile;
FILE *index_file;
char buff[BUFSIZ];

mkfilename(hfile_name, E_newsdir, "history");
hfile = fopen(hfile_name, "wb");
if (hfile == NULL) {
   printmsg(0, "Unable to create history file");
   return hfile;
}

strcpy(buff, H_VERSION);
strcat(buff, "\n");
fwrite(buff, sizeof(char), strlen(buff), hfile);

strcat(hfile_name, ".ndx");
index_file = fopen(hfile_name, "wb");
fwrite(buff, sizeof(char), strlen(buff), index_file);
sprintf(buff, "%s %9.9ld\n", history_date, ftell(hfile));
fwrite(buff, sizeof(char), strlen(buff), index_file);
fclose(index_file);

return hfile;
}



void init_history_dbm(void) {

#ifdef NEXT_RELEASE_MAYBE
int not_eof;
char *t;
long l;
int i;
int j;

int cur_block;

struct perf_elem *p;

char dbm_name[FILENAME_MAX];
char hbuf[BUFSIZ];         /* Buffer for each history file record */

   mkfilename(dbm_name, tempdir, "history.dbm");
   hdbm_file = fopen(dbm_name, "w+b");
   if (hdbm_file == NULL) {
      printmsg(0,"Unable to create history dbm file");
      exit(5);
   }

   for (i = 0; i < MAXbuf; i++) {
      hb[i] = malloc(BUFSIZ);
      checkref(hb[i]);
      hb_blk[i] = -1;
   }

   cur_block = 0;
   cur_buff = 0;
   cur_off = 0;

   /* Now build the file */
   fseek(hfile, (long)(strlen(H_VERSION)+1), SEEK_SET);  /* Back to the beginning */
   not_eof = TRUE;
   while (not_eof) {
      l = ftell(hfile);
      t = fgets(hbuf, BUFSIZ, hfile);
      if (t == NULL) {
         not_eof = FALSE;
      } else {
         if (cur_off >= (BUFSIZ/sizeof(struct perf_elem))) {
            fwrite(hb[cur_buff], sizeof(char), BUFSIZ, hdbm_file);
            hb_blk[cur_buff] = cur_block++;
            cur_off = 0;
            if (++cur_buff >= MAXbuf) {
               cur_buff = 0;
            }
            }
         p = (struct perf_elem *)hb[cur_buff];
         p += cur_off++;

         t = strchr(hbuf, ' ');
         if (t == NULL) t = hbuf;
         *t = '\0';

         p->where = l;
         p->IDlen = strlen(t);
         j = 0;
         for (i = p->IDlen-1; i >= 0; i--) {
            j = j + t[i];
         }
         p->cksum = (char)j;
            strncpy(p->IDpart, &(t[1]), sizeof(p->IDpart));
      }
   } /* while not eof */

   /* Dump out last (probably partial) block */
   if (cur_off < (BUFSIZ/sizeof(struct perf_elem))) {
      p = (struct perf_elem *)hb[cur_buff];
      p += cur_off;
      strnset((char *)p, '\0', (&hb[cur_buff][BUFSIZ] - &(char *)p));
   }
   fwrite(hb[cur_buff], sizeof(char), BUFSIZ, hdbm_file);
   hb_blk[cur_buff] = cur_block;
#endif

   return;
}

/*--------------------------------------------------------------------*/
/*    i s _ i n _ h i s t o r y                                       */
/*                                                                    */
/*    Check whether messageID is already in the history file.         */
/*--------------------------------------------------------------------*/

boolean is_in_history(FILE *hfile, char *messageID)
{

#ifdef NEXT_RELEASE_MAYBE
   int this_blk;

   struct perf_elem *p;

   if (hdbm_file == NULL) {
      /* Initialize */
      init_history_dbm();
   }

   /* First check the blocks in memory */
   this_blk = 0;
   cur_off = 0;

   while (this_blk < MAXbuf) {
      if (cur_off >= (BUFSIZ/sizeof(struct perf_elem))) {
         cur_off = 0;
         if (++this_blk >= MAXbuf) {
            break;
         }
         if (hb_blk[this_blk] == -1) break;
      }
      p = (struct perf_elem *)hb[this_blk];
      p += cur_off++;

      if (check_out_this_one(messageID, p)) {
         return TRUE;
       }
   }

   /* Now check out the rest of the file */
#else

/*--------------------------------------------------------------------*/
/*        Slow but true version extracted from rnews.c by ahd         */
/*--------------------------------------------------------------------*/

   boolean not_eof = TRUE;

   fseek(hfile, (long)(strlen(H_VERSION)+1), SEEK_SET);
                                          /* Back to the beginning */

/*--------------------------------------------------------------------*/
/*            Scan the entire disk file from the beginning            */
/*--------------------------------------------------------------------*/

   while (not_eof)
   {
      char hist_record[BUFSIZ];
      char *gc_ptr = fgets(hist_record, sizeof(hist_record), hfile);

      if (gc_ptr == NULL)
         not_eof = FALSE;
      else {
         gc_ptr = strchr(hist_record, ' ');
         if (gc_ptr == NULL)
            gc_ptr = hist_record;
         *gc_ptr = '\0';
         printmsg(9, "rnews:Comparing to history:%s", hist_record);
         if (equal(hist_record, messageID))
            return TRUE;
      } /* else */
    } /* while (not_eof) */

#endif

   return FALSE;
} /* is_in_history */


#ifdef NEXT_RELEASE_MAYBE

int check_out_this_one(char *m_id, struct perf_elem *p)
{

   printf("check_out_this_one called \n");
   return TRUE;

}

#endif
