home *** CD-ROM | disk | FTP | other *** search
/ Education Sampler 1992 [NeXTSTEP] / Education_1992_Sampler.iso / Programming / Source / WAIS / ir / irfiles.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-02-02  |  41.0 KB  |  1,443 lines

  1. /* WIDE AREA INFORMATION SERVER SOFTWARE:
  2.    No guarantees or restrictions.  See the readme file for the full standard
  3.    disclaimer.
  4.  
  5.    Brewster@think.com
  6. */
  7.  
  8. /* This file defines the files of an inverted file index.
  9.  *
  10.  * This structure is designed to be flexible rather than particularly
  11.  * optimized for speed or space.
  12.  * Thus this organization can support:
  13.  *   boolean, proximity, weights, and relevance feedback.
  14.  *
  15.  * Ported directly from the Lisp version 1.2 of the search engine.
  16.  *
  17.  * -brewster 6/90
  18.  */
  19.  
  20.  
  21. /* ==================== */
  22. /* ===  Change Log  === */
  23. /*Created 12/4/89 Brewster full lisp version
  24.  *split from ir-engine 1/11/90 brewster
  25.  *
  26.  *added memory indexing for efficiency
  27.  *added variable index block sizes
  28.  *5/90 ported to C
  29.  *5/90 split from irbuild.c
  30.  *7/90 declared truename() a static function - HWM 
  31.  *7/90 changed filename table and headline table to be null
  32.  *     terminated in the file rather than \newline.
  33.  *     compatibility problems between systems (sigh).
  34.  *     -brewster
  35.  *7/90 added field to document table for WAIStation
  36.  *     -brewster
  37.  *3/91 took out utilities and created futil.c -brewster
  38.  *3/91 took out the inverted file and created irinv.c -brewster
  39.  */
  40. /* ==================== */
  41.  
  42. /* ==================== */
  43. /*     To Do list       
  44.  *
  45.  * Implement a filename hashtable so that we can test quickly when
  46.  *   a file has been indexed.
  47.  * Free up all memory when we can. 
  48.  * Implement logrithmic merging
  49.  * 
  50.  */
  51.  
  52. /* change log
  53.  *  7/90 fixed: BUG: when adding words to the word disk hashtable, watch out 
  54.  *  for the end of the file and wrap.  If it is full, error out.
  55.  */
  56.  
  57. /* A specification for this is called ir-engine.text in microsoft word. */
  58.  
  59. #include <string.h> /* for memset() */
  60.  
  61. #include "cutil.h"
  62. #include "irfiles.h"
  63. #include "panic.h"
  64. #include "ustubs.h" /* for strstr */
  65. #include "futil.h"
  66. #include "sockets.h"
  67. #include "version.h"
  68.  
  69. #define PRINT_AS_INDEXING false /* also defined in irtfiles.c and irhash.c */
  70.  
  71. /*      -------------------------------    */
  72. #define DOC_TAB_HEADER_SIZE 2
  73. #define DOC_TAB_MAXIMUM_ENTRIES 8192
  74. #define DOC_TAB_ENTRY_FILENAME_ID_SIZE 3
  75. #define DOC_TAB_ENTRY_START_CHAR_SIZE 4
  76. #define DOC_TAB_ENTRY_END_CHAR_SIZE 4
  77. #define DOC_TAB_ENTRY_HEADLINE_ID_SIZE 3
  78. #define DOC_TAB_ENTRY_DOC_LENGTH_SIZE 4
  79. #define DOC_TAB_ENTRY_NUM_LINES_SIZE 3
  80. #define DOC_TAB_ENTRY_DATE_SIZE 4
  81. #define DOC_TAB_ELEMENT_SIZE 25 /* sum of above sizes */
  82.  
  83. #define DICTIONARY_ENTRY_SIZE 29 /* sum of MAX_WORD_LENGTH, 1 ('\0'), 
  84.                     NEXT_INDEX_BLOCK_SIZE and
  85.                     NUMBER_OF_OCCURANCES_SIZE */
  86.  
  87.  
  88. #define FILENAME_TABLE_HEADER_SIZE 4
  89. #define HEADLINE_TABLE_HEADER_SIZE 4
  90.  
  91. #define FILE_WRITE_DATE_SIZE 4
  92. #define NUMBER_OF_OCCURANCES_SIZE 4
  93. #define DOCUMENT_SCORE_LIMIT_SIZE 1
  94. #define DOCUMENT_SCORE_LIMIT 255  /* this is computed from DOCUMENT_SCORE_LIMIT_SIZE */
  95.  
  96. /*============================
  97.   ===   Database support   ===
  98.   ============================*/
  99.  
  100. database*     
  101. openDatabase(name,initialize,for_search)
  102. char* name;
  103. boolean initialize;
  104. boolean for_search;
  105. {
  106.   /* open a database (open all its files), and return an opaque object.
  107.      return NULL if there is an error
  108.    */
  109.   char file[MAX_FILE_NAME_LEN];
  110.   char open_mode[4];
  111.   database* db = (database*)s_malloc((size_t)sizeof(database));    
  112.       
  113.   if (for_search == true)
  114.     strncpy(open_mode,"rb",3); /* read only for searching */
  115.   else
  116.     strncpy(open_mode,"r+b",4); /* read/write for building */
  117.  
  118.   if (db == NULL)
  119.    { waislog(WLOG_HIGH, WLOG_ERROR,
  120.          "can't make a database, out of memory\n");
  121.      return(NULL);
  122.    }
  123.    
  124.   db->database_file = s_strdup(name);
  125.   if (db->database_file == NULL)
  126.    {     
  127.      waislog(WLOG_HIGH, WLOG_ERROR,
  128.          "can't make a database, out of memory\n");
  129.      disposeDatabase(db);
  130.      return(NULL);
  131.    }
  132.  
  133.   if(initialize == true){
  134.     initialize_index_files(db);
  135.   } 
  136.   else {
  137.     db->dictionary_stream = 
  138.       s_fopen(dictionary_filename(file, db),open_mode);
  139.     if (db->dictionary_stream == NULL) 
  140.       { waislog(WLOG_HIGH, WLOG_ERROR,"can't open the word hash file %s\n", file); 
  141.         disposeDatabase(db);
  142.     return(NULL);
  143.       }
  144.       
  145.     db->filename_table_stream =
  146.       s_fopen(filename_table_filename(file, db),open_mode);
  147.     if (db->filename_table_stream == NULL) 
  148.       { waislog(WLOG_HIGH, WLOG_ERROR,"can't open the filename file %s\n", file); 
  149.         disposeDatabase(db);
  150.     return(NULL);
  151.       }
  152.         
  153.     db->headline_table_stream = 
  154.       s_fopen(headline_table_filename(file, db),open_mode);
  155.     if (db->headline_table_stream == NULL) 
  156.       { waislog(WLOG_HIGH, WLOG_ERROR,"can't open the headline file %s\n", file); 
  157.         disposeDatabase(db);
  158.     return(NULL);
  159.       }
  160.         
  161.     db->document_table_stream = 
  162.       s_fopen(document_table_filename(file, db),open_mode);
  163.     if (db->document_table_stream == NULL) 
  164.       { waislog(WLOG_HIGH, WLOG_ERROR,"can't open the document id file %s\n", file); 
  165.         disposeDatabase(db);
  166.     return(NULL);
  167.       }
  168.       
  169.     /* initialize the allocated entries variable */
  170.     s_fseek(db->document_table_stream, 0L, SEEK_END);
  171.     db->doc_table_allocated_entries = 
  172.       (ftell(db->document_table_stream) - DOC_TAB_HEADER_SIZE) 
  173.     / DOC_TAB_ELEMENT_SIZE;
  174.     if(for_search){
  175.       db->index_stream = s_fopen(index_filename(file, db),open_mode);
  176.       if (db->index_stream == NULL) 
  177.     { waislog(WLOG_HIGH, WLOG_ERROR,"can't open the inverted index file %s\n", file);
  178.             disposeDatabase(db);
  179.       return(NULL);
  180.     }
  181.     }
  182.     else{
  183.       /* this is now done separately to be able to control the size: */
  184.       /* init_word_memory_hashtable(HASHTABLE_INITIAL_SIZE, db); */
  185.     }
  186.   }
  187.  
  188.   if(db->dictionary_stream != NULL){
  189.     db->dictionary_size = 
  190.       file_length(db->dictionary_stream) / DICTIONARY_ELEMENT_SIZE;
  191.   }
  192.   db->index_file_number = 0;
  193.   return(db);
  194. }
  195.  
  196.  
  197. void        
  198. closeDatabase(db)
  199. database* db;
  200. /* close a database and all its files. Do not dispose of the structure. */
  201. {
  202.   if (db == NULL)
  203.     return;
  204.   close_dictionary_file(db);
  205.   if (db->dictionary_stream != NULL)
  206.     s_fclose(db->dictionary_stream);
  207.   if (db->filename_table_stream != NULL)
  208.     s_fclose(db->filename_table_stream);
  209.   if (db->headline_table_stream != NULL)
  210.     s_fclose(db->headline_table_stream);
  211.   if (db->document_table_stream != NULL)
  212.     s_fclose(db->document_table_stream);
  213.   if (db->index_stream != NULL)
  214.     s_fclose(db->index_stream);
  215. }
  216.  
  217. void 
  218. disposeDatabase(db)
  219. database* db;
  220. {
  221.     closeDatabase(db);
  222.     s_free(db->database_file);
  223.     s_free(db);
  224. }
  225.  
  226. /* ==================================== */
  227. /* ===  Initialization of the files === */
  228. /* ==================================== */
  229.  
  230. #define BLOCK_SIZE 16384 /* size of blocks of zeros to write to a file */
  231.  
  232. static FILE* initialize_file _AP((long size,char* filename,boolean zero_it));
  233.  
  234. static FILE* initialize_file(size,filename,zero_it)
  235. long size;
  236. char* filename;
  237. boolean zero_it;
  238. /* initializes a file by opening a new stream, making it the right
  239.  * size and returning the stream.
  240.  */
  241. {
  242.   FILE* file = NULL;
  243.   long i;
  244.  
  245. #ifdef ANSI_LIKE
  246.   remove(filename);
  247. #endif
  248.  
  249.   file = s_fopen(filename, "wb");
  250.   if(NULL == file){ 
  251.     panic("The file %s could not be opened\n", filename);
  252.   }
  253.  
  254.   if(zero_it){
  255.     if(size >= BLOCK_SIZE){    /* then write big blocks of zeros */
  256.       char* zeros = NULL;
  257.       zeros = (char*)s_malloc((size_t)BLOCK_SIZE);
  258.       if(NULL == zeros){
  259.     panic("Could not allocate a large block of Zeros\n");
  260.       }
  261.       memset(zeros, 0, BLOCK_SIZE);
  262.       while(size >= BLOCK_SIZE){    
  263.     /* then write big blocks of zeros */
  264.     if(BLOCK_SIZE != fwrite(zeros, 1, BLOCK_SIZE, file))
  265.       panic("Write failed");
  266.     size = size - BLOCK_SIZE;
  267.       }
  268.       s_free(zeros);
  269.     }
  270.     for(i = 0; i < size; i++){    /* clean up the rest */
  271.       putc('\0', file); 
  272.     }
  273.   }    
  274.   else{                /* dont zero it */
  275.     grow_file(file, size);
  276.   }    
  277.  
  278. #ifdef THINK_C
  279.   /* set the mac file type to INDX */
  280.   setFileType(filename, WAIS_INDEX_FILE_TYPE, CREATOR);
  281. #endif                /* THINK_C */
  282.  
  283.   s_fclose(file);
  284.   file = s_fopen(filename, "r+b"); /* open it in read/write */
  285.   if(NULL == file){
  286.     panic("Error in initialization, can not reopen %s.\n", filename);
  287.   }
  288.   return(file);
  289. }
  290.  
  291. void initialize_index_files (db)
  292. database* db;
  293. /* This creates new index files, deleting any old ones. */
  294. {
  295.   char file[MAX_FILENAME_LEN];
  296.  
  297.   /* cprintf(PRINT_AS_INDEXING, "initializing index files: %s\n", db->database_file); */
  298.  
  299.   remove(dictionary_filename(file, db)); /* remove the old one */
  300.  
  301.   remove(index_filename(file, db)); /* remove the old one */
  302.   db->index_stream = NULL;
  303.  
  304.   db->doc_table_allocated_entries = 1; /* the 0th is the null pointer */
  305.   db->document_table_stream =
  306.     initialize_file((DOC_TAB_HEADER_SIZE + DOC_TAB_ELEMENT_SIZE),
  307.             document_table_filename(file, db), TRUE);
  308.   db->filename_table_stream =
  309.     initialize_file(FILENAME_TABLE_HEADER_SIZE,
  310.             filename_table_filename(file, db), TRUE);
  311.   db->headline_table_stream =
  312.     initialize_file(HEADLINE_TABLE_HEADER_SIZE,
  313.             headline_table_filename(file, db), TRUE);
  314. }
  315.  
  316. /* ========================= */
  317. /* ===  Dictionary File  === */
  318. /* ========================= */
  319.  
  320. /* The dictionary file is a 1 deep tree of blocks.  
  321.    The header of the file says how long the header block is.
  322.    The "header block" is a set of pointers to the heads of
  323.    the blocks in the dictionary.
  324.  
  325.    A dictionary block is a list of word and pointer pairs.  The words
  326.    are padded to a fixed length so that it is a fixed length record.
  327.    The pointers are pointers into the inverted file (except in the header
  328.    block where they are pointers into the dictionary file).
  329. */
  330.  
  331. /*  SEARCHING DICTIONARY FILES */
  332.    
  333. /* top level function:
  334.    long look_up_word_in_dictionary(char *word,database* db) 
  335.  */
  336.  
  337. unsigned char *dictionary_header_block = NULL; /* the dictionary header. 
  338.                           loaded once */
  339.  
  340. long number_of_dictionary_blocks = 0;  /* also the length of the dictionary 
  341.                       header block */
  342.  
  343. unsigned char *dictionary_block = NULL; /* this is one of the dict blocks */
  344.  
  345. void close_dictionary_file(db)
  346.      database *db;
  347. {
  348.   if(dictionary_header_block) s_free(dictionary_header_block);
  349.   dictionary_header_block = NULL;
  350. }
  351.   
  352.   
  353. static long fread_from_stream _AP((FILE* stream,unsigned char* buf,
  354.                    long nbytes));
  355.  
  356. static long fread_from_stream(stream,buf,nbytes)
  357. FILE *stream;
  358. unsigned char *buf;
  359. long nbytes;
  360. /* this is a safe version of unix 'fread' it does all the checking
  361.  * and looping necessary
  362.  */
  363. {
  364.   long didRead;
  365.   long toRead = nbytes;
  366.   long totalRead = 0;        /* paranoia */
  367.   /*printf("in Fread_from_stream buffer %ld, nbytes %ld\n", (long)buf, nbytes); */
  368.  
  369.   while (toRead > 0){
  370.     didRead = fread(buf, sizeof(char), toRead, stream);
  371.     if(didRead == -1)        /* error*/
  372.       return(-1);
  373.     if(didRead == 0)        /* eof */
  374.       return(-2);        /* maybe this should return 0? */
  375.     toRead -= didRead;
  376.     buf += didRead;
  377.     totalRead += didRead;
  378.   }
  379.   if(totalRead != nbytes)    /* we overread for some reason */
  380.     return(- totalRead);    /* bad news */    
  381.   return(totalRead);
  382. }
  383.  
  384. char *dictionary_block_word(i,block)
  385. long i;
  386. unsigned char *block;
  387. /* returns the word field in the ith dictionary block entry */
  388. {
  389.   return((char *)(block + (i * DICTIONARY_ENTRY_SIZE)));
  390. }
  391.  
  392. long dictionary_block_position(i,block)
  393. long i;
  394. unsigned char *block;
  395. /* returns the position field in the ith dictionary block entry */
  396. {
  397.   /* printf("dictionary_block_position %ld\n",
  398.      read_bytes_from_memory
  399.      (NEXT_INDEX_BLOCK_SIZE,
  400.       block + (i * DICTIONARY_ENTRY_SIZE) + 
  401.       MAX_WORD_LENGTH + 1)); */
  402.   return(read_bytes_from_memory
  403.      (NEXT_INDEX_BLOCK_SIZE,
  404.       block + (i * DICTIONARY_ENTRY_SIZE) + 
  405.       MAX_WORD_LENGTH + 1));
  406. }
  407.  
  408. long dictionary_block_word_occurances(i,block)
  409. long i;
  410. unsigned char *block;
  411. /* returns the occurances field in the ith dictionary block entry */
  412. {
  413.   return(read_bytes_from_memory
  414.      (NEXT_INDEX_BLOCK_SIZE,
  415.       block + (i * DICTIONARY_ENTRY_SIZE) + 
  416.       MAX_WORD_LENGTH + 1 + NEXT_INDEX_BLOCK_SIZE));
  417. }
  418.  
  419. static long find_pointer_in_block _AP((char* word,unsigned char* block,
  420.                        long block_length));
  421.  
  422. static long find_pointer_in_block(word,block,block_length)
  423. char *word;
  424. unsigned char *block;
  425. long block_length; /* in entries */ 
  426. /* returns 0 if an error or if the word is below the lowest block,
  427.    (this confusion between error and NULL is bad, but found late in the 
  428.    design process)
  429.    it returns the positive position if the word is there exactly,
  430.    and the negative of the position of the word before it if the
  431.    word is not there exactly.
  432. */
  433. {
  434.   /* find the entry in the dictionary header for this word.
  435.      returns 0 if not found. */
  436.   /* this could be binary search XXX */
  437.   long i;
  438.   for(i = 0; i < block_length; i++){
  439.     long compare;
  440.     char *dictionary_word = dictionary_block_word(i, block);
  441.     if(dictionary_word[0] == '\0')
  442.       break;
  443.     compare = strcmp(dictionary_word, word);
  444.     if(0 == compare)
  445.       return(dictionary_block_position(i, block));
  446.     if(compare > 0){
  447.       if(i == 0)
  448.     return(0);
  449.       else return(- dictionary_block_position(i - 1, block));
  450.     }
  451.   }
  452.   if(i == 0)
  453.     return(0);
  454.   else return(- dictionary_block_position(i - 1, block));
  455. }
  456.  
  457. unsigned char *read_dictionary_block(block,position,length,stream)
  458. unsigned char *block;
  459. long position;
  460. long length;
  461. FILE *stream;
  462. /* reads the dictionary block from the disk and returns it.
  463.    block is the place to put it, if it is NULL, then it is malloc'ed.
  464.    position is the position in the dictionary file to start reading.
  465.    length is th enumber of entries (not bytes) in the block.
  466.    stream is the dictionary stream.
  467.    
  468.    it returns NULL if it loses.
  469.  */
  470.     
  471. {
  472.   if(NULL == block)
  473.     block = (unsigned char *)s_malloc((size_t)(length * DICTIONARY_ENTRY_SIZE));
  474.   s_fseek(stream, position, SEEK_SET);
  475.   if(0 > fread_from_stream(stream, block, (length * DICTIONARY_ENTRY_SIZE))){
  476.     waislog(WLOG_HIGH, WLOG_ERROR,
  477.         "Could not read the dictionary block %ld, length %ld",
  478.         block, length);
  479.     return(NULL);
  480.   }
  481.   return(block);
  482. }
  483.  
  484. long 
  485. look_up_word_in_dictionary(word,db)
  486. char *word;
  487. database* db;
  488. /* looks up the word in the dictionary file. Returns the pointer
  489.    into the inverted file or negative number if not found, 
  490.    or 0 if error.
  491.  */
  492. {
  493.   FILE *stream = db->dictionary_stream;
  494.   long dictionary_block_pos;
  495.  
  496.   if(NULL == dictionary_header_block)
  497.    {
  498.      s_fseek(stream, 0L, SEEK_SET);
  499.      number_of_dictionary_blocks = read_bytes(DICTIONARY_HEADER_SIZE,stream);
  500.      dictionary_header_block =
  501.        read_dictionary_block(dictionary_header_block,DICTIONARY_HEADER_SIZE,
  502.                  number_of_dictionary_blocks,stream);
  503.      if(NULL == dictionary_header_block)
  504.       {    waislog(WLOG_HIGH, WLOG_ERROR,
  505.         "Could not read dictionary header block in db %s.",
  506.         db->database_file);
  507.     return(0);
  508.       }
  509.    }
  510.  
  511.   dictionary_block_pos = 
  512.     find_pointer_in_block(word, dictionary_header_block,
  513.               number_of_dictionary_blocks);
  514.   if(0 == dictionary_block_pos)
  515.    { /* waislog(WLOG_HIGH, WLOG_ERROR, "Could not find pointer for word '%s' (location %ld) in block in db %s!",
  516.          word, word, db->database_file); */
  517.      return(-1);  /* not an error, necessarily if the word is before the first entry */
  518.    }
  519.  
  520.   dictionary_block = 
  521.     read_dictionary_block(dictionary_block,ABS(dictionary_block_pos),
  522.               DICTIONARY_BLOCK_SIZE,stream);
  523.   if(NULL == dictionary_block)
  524.    { waislog(WLOG_HIGH, WLOG_ERROR,
  525.          "Could not read dictionary block %ld in db %s",
  526.          ABS(dictionary_block_pos),
  527.          db->database_file);
  528.      return(0);
  529.    }
  530.  
  531.   return(find_pointer_in_block(word, dictionary_block,DICTIONARY_BLOCK_SIZE));
  532. }
  533.  
  534.  
  535. /*  BUILDING DICTIONARY FILES */
  536.  
  537.  
  538. long number_of_dictionary_entries; /* number allocated */
  539.  
  540. char *block_of_zeros = NULL;
  541.  
  542. static void write_zeros_to_stream _AP((long n_bytes,FILE* stream));
  543.  
  544. static void write_zeros_to_stream(n_bytes,stream)
  545. long n_bytes;
  546. FILE *stream;
  547. /* writes zeros to a file quickly */
  548. {    
  549.   long i;
  550.   if(n_bytes >= BLOCK_SIZE){    /* then write big blocks of zeros */
  551.     if(NULL == block_of_zeros){
  552.       block_of_zeros = (char*)s_malloc((size_t)BLOCK_SIZE);
  553.       memset(block_of_zeros, 0, BLOCK_SIZE);
  554.     }
  555.     while(n_bytes >= BLOCK_SIZE){    
  556.       /* then write big blocks of zeros */
  557.       if(BLOCK_SIZE != 
  558.      fwrite(block_of_zeros, sizeof(char), BLOCK_SIZE, stream))
  559.     panic("Write failed");
  560.       n_bytes -= BLOCK_SIZE;
  561.     }
  562.   }
  563.   for(i = 0; i < n_bytes; i++){    /* clean up the rest */
  564.     putc('\0', stream); 
  565.   }
  566. }    
  567.  
  568. /* returns 0 if successful */
  569. long init_dict_file_for_writing(db)
  570. database *db;
  571. {
  572.   char filename[MAX_FILENAME_LEN];
  573.   long number_of_blocks;
  574.  
  575. /*printf("init_dict_file_for_writing\n");*/
  576.   if (db->dictionary_stream == NULL)
  577.     db->dictionary_stream = s_fopen(dictionary_filename(filename, db),  "w+b");
  578.  
  579.   number_of_blocks = (db->number_of_words / DICTIONARY_BLOCK_SIZE);
  580.   number_of_blocks +=  /* must be done on separate lines for XENIX */
  581.     ((0 == (db->number_of_words % DICTIONARY_BLOCK_SIZE)) ? 0 : 1);
  582. /*printf("number of words = %ld, block size = %ld, number-of blocks = %ld\n",db->number_of_words,DICTIONARY_BLOCK_SIZE,number_of_blocks);*/
  583.  
  584.   init_dict_file_detailed(db->dictionary_stream,number_of_blocks);
  585.   return(0);
  586. }
  587.  
  588. void
  589. init_dict_file_detailed(dictionary_stream,number_of_blocks)
  590. FILE* dictionary_stream;
  591. long number_of_blocks;
  592. {
  593.   /* create space for the table in the front of the file */
  594.   write_zeros_to_stream(DICTIONARY_HEADER_SIZE + 
  595.             DICTIONARY_ENTRY_SIZE * number_of_blocks,
  596.             dictionary_stream);
  597.   /* write the number of blocks */
  598.   s_fseek(dictionary_stream, 0L, SEEK_SET);
  599.   write_bytes(number_of_blocks, DICTIONARY_HEADER_SIZE, dictionary_stream);
  600.   
  601.   fseek(dictionary_stream, 0L, SEEK_END);
  602.   number_of_dictionary_entries = 0;
  603. }
  604.     
  605. long add_word_to_dictionary(word,position,number_of_occurances,db)
  606. char *word;
  607. long position;
  608. long number_of_occurances;
  609. database *db;
  610.      /* Puts a word into the dictionary file. */
  611. {
  612.   /* assumes the streamg has been initialized, and it is positioned
  613.      at the end */
  614.   FILE *stream = db->dictionary_stream;
  615.   char padded_word[MAX_WORD_LENGTH + 1];
  616.  
  617.   memset(padded_word, 0, MAX_WORD_LENGTH + 1); /* clear the word */
  618.   strcpy(padded_word, word);
  619.  
  620.   if(0 == (number_of_dictionary_entries % DICTIONARY_BLOCK_SIZE)){
  621.     /* then add an entry in the header */
  622.     long original_position = s_ftell(stream);
  623.     long header_entry = number_of_dictionary_entries / DICTIONARY_BLOCK_SIZE; 
  624.     /* printf("Adding header entry %ld %s original pos %ld\n", 
  625.        header_entry, padded_word, original_position); */
  626.     fseek(stream, DICTIONARY_HEADER_SIZE + 
  627.       (header_entry * DICTIONARY_ENTRY_SIZE), SEEK_SET);
  628.     if((MAX_WORD_LENGTH + 1) != 
  629.        fwrite(padded_word, sizeof(char), MAX_WORD_LENGTH + 1, stream))
  630.       panic("Write failed");
  631.     write_bytes(original_position, NEXT_INDEX_BLOCK_SIZE, stream);
  632.     write_bytes(0L, NUMBER_OF_OCCURANCES_SIZE, stream);
  633.     fseek(stream, original_position, SEEK_SET); /* go back to the end */
  634.     /* zero the next block */
  635.     write_zeros_to_stream(DICTIONARY_ENTRY_SIZE * DICTIONARY_BLOCK_SIZE,
  636.               stream); 
  637.     fseek(stream, original_position, SEEK_SET);      
  638.   }
  639.   /* write the word */    
  640.   if((MAX_WORD_LENGTH + 1) !=
  641.      fwrite(padded_word, sizeof(char), MAX_WORD_LENGTH + 1, stream))
  642.     panic("Write failed");
  643.   write_bytes(position, NEXT_INDEX_BLOCK_SIZE, stream);
  644.   write_bytes(number_of_occurances, NUMBER_OF_OCCURANCES_SIZE, stream);
  645.   number_of_dictionary_entries++;    
  646.   return(0);
  647. }
  648.  
  649. void print_dictionary_block(block,size)
  650. unsigned char *block;
  651. long size;
  652. /* this prints the contents of a dictionary block */
  653. {
  654.   long i;
  655.   for(i = 0; i < size; i++){
  656.     char *word = dictionary_block_word(i, block);
  657.     if(word[0] == '\0')
  658.       break;
  659.     /* I assume this is only for debugging - JG */
  660.     printf("Entry %3ld: %21s %7ld %7ld\n", i, word,
  661.         dictionary_block_position(i, block),
  662.         dictionary_block_word_occurances(i, block));
  663.   }
  664. }
  665.  
  666. void print_dictionary _AP((database* db));
  667.   
  668. void print_dictionary(db)
  669. database *db;
  670. {
  671.   /* prints the contents of a dictionary */
  672.   FILE *stream = db->dictionary_stream;
  673.   long i;
  674.   long new_number_of_dictionary_blocks;
  675.  
  676.   if(NULL == stream)
  677.     panic("dictionary stream is not open");
  678.   s_fseek(stream, 0L, SEEK_SET);
  679.   new_number_of_dictionary_blocks = read_bytes(DICTIONARY_HEADER_SIZE, stream);
  680.   if(new_number_of_dictionary_blocks > number_of_dictionary_blocks)
  681.     dictionary_header_block = NULL;
  682.   number_of_dictionary_blocks = new_number_of_dictionary_blocks;
  683.   printf("Number of dictionary blocks %ld\n", number_of_dictionary_blocks);
  684.   if(NULL == (dictionary_header_block =
  685.           read_dictionary_block(dictionary_header_block,
  686.                     DICTIONARY_HEADER_SIZE,
  687.                     number_of_dictionary_blocks,
  688.                     stream)))
  689.     panic("Could not read dictionary header block");
  690.   printf("The Dictionary Header Block:\n");
  691.   print_dictionary_block(dictionary_header_block, number_of_dictionary_blocks);
  692.   for(i = 0; i < number_of_dictionary_blocks; i++){
  693.     long pos = dictionary_block_position(i, dictionary_header_block);
  694.     if(NULL == (dictionary_block =
  695.         read_dictionary_block(dictionary_block,
  696.                       pos, DICTIONARY_BLOCK_SIZE, stream)))
  697.       panic("Could not read dictionary block %ld", pos);
  698.     printf("\n\nDictionary block %ld (position %ld):\n", i, pos);
  699.     print_dictionary_block(dictionary_block, DICTIONARY_BLOCK_SIZE);
  700.   }
  701.   fseek(stream, 0L, SEEK_END);
  702. }
  703.  
  704. #ifdef testing
  705. /* dictionary testing code */
  706.  
  707. static void check_dictionary_entry _AP((char* word,long expected_position,
  708.                     database* db));
  709.  
  710. static void check_dictionary_entry(word,expected_position,db)
  711. char *word;
  712. long expected_position;
  713. database *db;
  714. {
  715.   if(expected_position != look_up_word_in_dictionary(word, db)) {
  716.     waislog(WLOG_HIGH, WLOG_ERROR,
  717.         "%s should be %ld is %ld in db %s\n", 
  718.         word, expected_position, 
  719.         look_up_word_in_dictionary(word, db),
  720.         db->database_file);
  721.   }
  722. }
  723.   
  724. static void test_dictionary _AP((database* db));
  725.  
  726. static void test_dictionary(db)
  727. database *db;
  728. /* this is just an trivial test */
  729. {
  730.  
  731.   db->number_of_words = 3;
  732.   init_dict_file_for_writing(db);
  733.   add_word_to_dictionary("aardvark", 123L, 0l, db);
  734.   add_word_to_dictionary("house", 234L, 0L, db);
  735.   add_word_to_dictionary("mary", 345L, 0L, db);
  736.   fflush(db->dictionary_stream);
  737.   print_dictionary(db);
  738.   check_dictionary_entry("aardvark", 123L, db);
  739.   check_dictionary_entry("house", 234L, db);
  740.   check_dictionary_entry("mary", 345L, db);
  741.   check_dictionary_entry("food", -123L, db);
  742.   check_dictionary_entry("zebra", -345L, db);
  743.   check_dictionary_entry("aaarf", 0L, db);
  744. }
  745. #endif /* def testing */
  746.  
  747.  
  748. /*========================*
  749.  *===  Document Table  ===*
  750.  *========================*/
  751.  
  752. boolean
  753. read_document_table_entry(doc_entry,number,db)
  754. document_table_entry* doc_entry;
  755. long number;
  756. database* db;
  757. /* returns a document_table_entry on the stack */
  758. {
  759.   long position;
  760.   FILE *stream = db->document_table_stream;
  761.     
  762.   position = (DOC_TAB_HEADER_SIZE + 
  763.           ((long)number * (long)DOC_TAB_ELEMENT_SIZE));
  764.  
  765.   if (0 != fseek(stream, position, SEEK_SET))
  766.     { 
  767.       waislog(WLOG_HIGH, WLOG_ERROR,
  768.           "fseek failed into the document table to position %ld in db %s", 
  769.           position,
  770.           db->database_file);
  771.       return(false);
  772.     }
  773.     
  774.   doc_entry->filename_id = read_bytes(DOC_TAB_ENTRY_FILENAME_ID_SIZE, 
  775.                      stream);
  776.   doc_entry->headline_id = read_bytes(DOC_TAB_ENTRY_HEADLINE_ID_SIZE, 
  777.                      stream);    
  778.   doc_entry->start_character = 
  779.     read_bytes(DOC_TAB_ENTRY_START_CHAR_SIZE, stream);
  780.   doc_entry->end_character = 
  781.     read_bytes(DOC_TAB_ENTRY_END_CHAR_SIZE, stream);
  782.   doc_entry->document_length = 
  783.     read_bytes(DOC_TAB_ENTRY_DOC_LENGTH_SIZE, stream);
  784.   doc_entry->number_of_lines = 
  785.     read_bytes(DOC_TAB_ENTRY_NUM_LINES_SIZE, stream);
  786.   doc_entry->date = 
  787.     read_bytes(DOC_TAB_ENTRY_DATE_SIZE, stream);
  788.   if (doc_entry->date == EOF) { 
  789.     waislog(WLOG_HIGH, WLOG_ERROR,
  790.         "Reading from the Document Table got an error in db %s",
  791.         db->database_file);
  792.     return(false);
  793.   }
  794.  
  795.   return(true);
  796. }
  797.  
  798. #ifdef testing
  799.  
  800. static  boolean check_document_id _AP((long doc_id,database* db));
  801.  
  802. static  boolean
  803. check_document_id(doc_id,db)
  804. long doc_id;
  805. database* db;
  806. /* returns true if that is a valid doc_id (corresponds to a file
  807.    that has not been deleted */
  808. {
  809.   long position;
  810.   FILE *stream = db->document_table_stream;
  811.   long filename_id;
  812.   char filename[MAX_FILE_NAME_LEN];
  813.  
  814.   position = (DOC_TAB_HEADER_SIZE + 
  815.           ((long)doc_id * (long)DOC_TAB_ELEMENT_SIZE));
  816.  
  817.   if (0 != fseek(stream, position, SEEK_SET)) { 
  818.     waislog(WLOG_HIGH, WLOG_ERROR,
  819.         "fseek failed into the document table to position %ld in db %s",
  820.         position,
  821.         db->database_file);
  822.     return(false);
  823.   }
  824.     
  825.   filename_id = read_bytes(DOC_TAB_ENTRY_FILENAME_ID_SIZE, stream);
  826.   /* probe the file.  Is there a faster way? */
  827.   return(probe_file(read_filename_table_entry(filename_id, filename,
  828.                           NULL,
  829.                           db)));
  830. }
  831. #endif
  832.  
  833. long write_document_table_entry(doc_table_entry, db)
  834. document_table_entry* doc_table_entry;
  835. database* db;
  836. {
  837.   /* returns the document_id */
  838.   s_fseek(db->document_table_stream,
  839.          (DOC_TAB_HEADER_SIZE +
  840.           (db->doc_table_allocated_entries *
  841.            DOC_TAB_ELEMENT_SIZE)),
  842.          SEEK_SET);
  843.   /* write the pieces */
  844.   write_bytes(doc_table_entry->filename_id,
  845.           DOC_TAB_ENTRY_FILENAME_ID_SIZE,
  846.           db->document_table_stream);
  847.   write_bytes(doc_table_entry->headline_id,
  848.           DOC_TAB_ENTRY_HEADLINE_ID_SIZE,
  849.           db->document_table_stream);
  850.   write_bytes(doc_table_entry->start_character,
  851.           DOC_TAB_ENTRY_START_CHAR_SIZE,
  852.           db->document_table_stream);
  853.   write_bytes(doc_table_entry->end_character,
  854.           DOC_TAB_ENTRY_END_CHAR_SIZE,
  855.           db->document_table_stream);
  856.   write_bytes(doc_table_entry->document_length,
  857.           DOC_TAB_ENTRY_DOC_LENGTH_SIZE,
  858.           db->document_table_stream);
  859.   /*  printf("Writing %ld lines\n", document_table_entry->number_of_lines); */
  860.   write_bytes(doc_table_entry->number_of_lines,
  861.           DOC_TAB_ENTRY_NUM_LINES_SIZE,
  862.           db->document_table_stream);
  863.   write_bytes(doc_table_entry->date,
  864.           DOC_TAB_ENTRY_DATE_SIZE,
  865.           db->document_table_stream);
  866.   db->doc_table_allocated_entries++;
  867.   return(db->doc_table_allocated_entries);
  868. }
  869.  
  870. long next_document_id(db)
  871. database* db;
  872. {
  873.   return(db->doc_table_allocated_entries);
  874. }
  875.  
  876.  
  877. /*========================*
  878.  *===  Filename table  ===*
  879.  *========================*/
  880.  
  881. #ifndef MAXPATHLEN /* think_c does not define it for instance */
  882. #define MAXPATHLEN 2000
  883. #endif /* MAXPATHLEN */
  884.  
  885. static char *read_filename_table_stream _AP((long position, 
  886.                         char* filename,
  887.                         char* type, 
  888.                         time_t* file_write_date,
  889.                         FILE *stream));
  890.  
  891. static char *read_filename_table_stream(position,filename,type,
  892.                     file_write_date, stream)
  893. long position;
  894. char* filename;
  895. char* type;
  896. time_t* file_write_date;
  897. FILE *stream;
  898. {
  899.   /* Returns the filename array after side effecting it,
  900.    *  or NULL if an error.
  901.    * The type of the file is put in the argument "type".  This will
  902.    * not be longer than MAX_FILE_NAME_LEN.
  903.    *
  904.    * if type is NULL then ignore it,
  905.    * if file_write_date is NULL then ignore it,
  906.    * If position is -1, then it does not seek.
  907.    *
  908.    * Leave the file positioned at the start of the next entry.
  909.    */    
  910.   long file_write_date_internal;
  911.   char type_internal[MAX_TYPE_LEN];
  912.  
  913.   if(NULL == stream)
  914.     return(NULL);
  915.  
  916.   if(NULL == type)  /* this means we do not care, so set up a dummy */
  917.     type = type_internal;
  918.  
  919.   filename[0] = '\0';    /* init to the empty string */
  920.   if(NULL != type)
  921.     type[0] = '\0';    /* init to the empty string */
  922.  
  923.   if(position != -1){
  924.     if (0 != fseek(stream, position, SEEK_SET)){
  925.       waislog(WLOG_HIGH, WLOG_ERROR, "fseek failed into the filename index to position %ld", 
  926.           position);
  927.       return(NULL);
  928.     }
  929.   }
  930.   if(false == read_string_from_file(stream, filename, MAX_FILE_NAME_LEN)){
  931.     return(NULL);
  932.   }
  933.   else{
  934.     file_write_date_internal = read_bytes(FILE_WRITE_DATE_SIZE, stream);
  935.     if(file_write_date){
  936.       *file_write_date = (time_t)file_write_date_internal;
  937.     }
  938.     if(false == read_string_from_file(stream, type, MAX_TYPE_LEN)){
  939.       return(NULL);
  940.     } 
  941.   }
  942.   return(filename);
  943. }
  944.      
  945. char *read_filename_table_entry(position,filename,type,file_write_date,db)
  946. long position;
  947. char* filename;
  948. char* type;
  949. time_t* file_write_date;
  950. database* db;
  951. {
  952.   /* Returns the filename array after side effecting it,
  953.    *  or NULL if an error.
  954.    * The type of the file is put in the argument "type".  This will
  955.    * not be longer than MAX_FILE_NAME_LEN.
  956.    *
  957.    * if type is NULL then ignore it,
  958.    * if file_write_date is NULL then ignore it,
  959.    * If position is -1, then it does not seek.
  960.    *
  961.    * Leave the file positioned at the start of the next entry.
  962.    */    
  963.   FILE *stream = db->filename_table_stream;
  964.   return(read_filename_table_stream(position,filename,type,
  965.                     file_write_date,stream));
  966. }
  967.  
  968. long write_filename_table_entry(filename,type,db)
  969. char* filename;
  970. char *type;
  971. database* db;
  972. {
  973.   /* writes the filename (NULL terminated),
  974.      followed by 4 bytes of creation date,
  975.      followed by the file type (NULL terminated),
  976.      Returns the postion of the filename
  977.      */
  978.   long free_position;
  979.   char full_path[MAXPATHLEN];
  980.   s_fseek(db->filename_table_stream, 0L, SEEK_END);
  981.   free_position = ftell(db->filename_table_stream);
  982.   /* add the filename to the hashtable not done yet XXX
  983.      (setf (gethash filename *filename_table_hashtable*)
  984.      (file_write_date filename))
  985.      */
  986.   fprintf(db->filename_table_stream, "%s", truename(filename, full_path));
  987.   fputc(0, db->filename_table_stream);
  988.   if(FILE_WRITE_DATE_SIZE != sizeof(time_t)){ /* check if these are the same */
  989.     panic("We have a problem with the file_write_date_size\n");
  990.   }
  991.   write_bytes((long)file_write_date(filename),
  992.               FILE_WRITE_DATE_SIZE, db->filename_table_stream);
  993. /*  fwrite(type, sizeof(char), strlen(type) + 1, db->filename_table_stream);*/
  994.   fprintf(db->filename_table_stream, "%s",type);
  995.   fputc(0,db->filename_table_stream);
  996.   return(free_position);
  997. }
  998.  
  999. /* functions to figure out if the file is in the index already */
  1000.         
  1001. static boolean filename_in_filename_stream _AP((char *filename, char *type, 
  1002.                         time_t *file_write_date, 
  1003.                         FILE *stream));
  1004.              
  1005. static boolean filename_in_filename_stream(filename, type, 
  1006.                        file_write_date, stream)
  1007. char *filename;
  1008. char *type;
  1009. time_t *file_write_date;
  1010. FILE *stream;
  1011.      /* returns true if it is there (and side effects type and 
  1012.       file_write_date). 
  1013.         leaves the stream at the end of the file.
  1014.     If type or file_write_date is NULL, then it is a dont care.
  1015.     type, if it is an array, should be MAX_FILENAME_LEN long at least.
  1016.     */
  1017. {
  1018.   /* this is slow because it loops through the whole file every time.
  1019.      this might want to be optimized by making a hashtable. */
  1020.   char next_filename[MAX_FILENAME_LEN];
  1021.   
  1022.   s_fseek(stream, FILENAME_TABLE_HEADER_SIZE, SEEK_SET);
  1023.   while(!feof(stream)){
  1024.     char new_type[MAX_FILENAME_LEN];
  1025.     if(NULL == 
  1026.        read_filename_table_stream(-1, next_filename, new_type, 
  1027.                   file_write_date, stream))
  1028.       return(false);
  1029.     if(0 == strcmp(next_filename, filename))
  1030.       return(true);
  1031.   }
  1032. }
  1033.  
  1034. boolean filename_in_database(filename,type,file_write_date,db)
  1035. char *filename;
  1036. char *type;
  1037. time_t *file_write_date;
  1038. database *db;
  1039. {
  1040.   return(filename_in_filename_stream(filename, type, file_write_date, 
  1041.                      db->filename_table_stream));
  1042. }
  1043.  
  1044. /* this caches the last filename that was found to be in the filename file,
  1045.    this way repeated attempts to figure out if a file is there will be fast.
  1046.    This is the case when retrieving successive blocks of a file. */   
  1047. char last_filename_found_in_file[MAX_FILE_NAME_LEN];
  1048. char last_filename_file[MAX_FILE_NAME_LEN];
  1049.  
  1050. boolean filename_in_filename_file(filename,type,file_write_date, filename_file)
  1051. char *filename;
  1052. char *type;
  1053. time_t *file_write_date;
  1054. char *filename_file;
  1055. {
  1056.   if(NULL == filename)
  1057.     return(false);
  1058.   
  1059.   if(0 == strcmp(last_filename_found_in_file, filename) &&
  1060.      0 == strcmp(last_filename_file, filename_file))
  1061.     return(true);
  1062.   else
  1063.    { FILE *stream = s_fopen(filename_file, "r");
  1064.      boolean answer;
  1065.  
  1066.      if(NULL == stream)
  1067.       { s_fclose(stream);
  1068.     return(false);
  1069.       }
  1070.      answer = 
  1071.        filename_in_filename_stream(filename,type,file_write_date, stream);
  1072.      if(answer == true)
  1073.       { /* record it in the cache */
  1074.     strncpy(last_filename_file, filename_file, MAX_FILE_NAME_LEN);
  1075.     strncpy(last_filename_found_in_file, filename, MAX_FILE_NAME_LEN);
  1076.       }
  1077.      s_fclose(stream);
  1078.      return(answer);
  1079.    }
  1080. }
  1081.  
  1082.  
  1083. /*========================*
  1084.  *===  Headline Table  ===*
  1085.  *========================*/
  1086.  
  1087. char *read_headline_table_entry(position,db)
  1088. long position;
  1089. database* db;
  1090.   /* returns the headline array after side effecting it.  Beware that 
  1091.    * the next call to this function will overwrite the the headline_array
  1092.    */
  1093. {
  1094.   /* this is the headline that gets returned */
  1095.   static char headline_array[MAX_HEADLINE_LEN]; 
  1096.   FILE *stream = db->headline_table_stream;
  1097.   headline_array[0] = '\0';    /* init to the empty string */
  1098.     
  1099.   if (0 != fseek(stream, position, SEEK_SET)) { 
  1100.     waislog(WLOG_HIGH, WLOG_ERROR, 
  1101.         "fseek failed into the headline index to position %ld in db %s", 
  1102.         position, db->database_file);
  1103.     return(headline_array);
  1104.   }
  1105.   if(false == read_string_from_file(db->headline_table_stream, 
  1106.                     headline_array, MAX_FILE_NAME_LEN)){
  1107.     waislog(WLOG_HIGH, WLOG_ERROR, 
  1108.         "headline table is corrupt at %ld in db %s", 
  1109.         position, db->database_file);
  1110.   }
  1111.   return(headline_array);
  1112. }
  1113.  
  1114. /* writes the string to the file followed by a NULL.
  1115.  * The returned number is the position in the file to start reading.
  1116.  */
  1117. long write_headline_table_entry(headline,db)
  1118. char* headline;
  1119. database* db;
  1120. {
  1121.   /* writes the headline followed by a newline.
  1122.      Returns the postion of the headline.
  1123.      */
  1124.   long free_position;
  1125.   s_fseek(db->headline_table_stream, 0L, SEEK_END);
  1126.   free_position = ftell(db->headline_table_stream);
  1127.  
  1128.   fprintf(db->headline_table_stream, "%s", headline);
  1129.   fputc(0, db->headline_table_stream);
  1130.   return(free_position);
  1131. }
  1132.  
  1133.  
  1134. /* =================== */
  1135. /* === Source file === */
  1136. /* =================== */
  1137.  
  1138. /* the source file is an ascii file for describing a source.
  1139.    it is defined in ../doc/source.txt */
  1140.  
  1141. /* Registers the src structure with the directory of servers.
  1142.    Return true if successful */
  1143. boolean register_src_structure(filename)
  1144. char *filename;
  1145. {
  1146.   char string[200];
  1147.   long answer;
  1148.   /* register the server with the directory of servers */
  1149.   printf("Sending source struture to the directory of servers...");
  1150.   fflush(stdout);
  1151.   sprintf(string,
  1152.       "cat %s | mail wais-directory-of-servers@quake.think.com\n", 
  1153.       filename);
  1154.   answer = system(string);
  1155.   printf("Done.\n");      
  1156.   return((answer == 0)?true:false);
  1157. }
  1158.  
  1159.  
  1160. /* Writes a source structure to a file.
  1161.    If the export_database arg is set, then the tcp_port is used in the 
  1162.    tcp-port slot.
  1163.    Returns true if successful. */
  1164. boolean write_src_structure(filename, database_name, typename, 
  1165.                 filenames, count, export_database, tcp_port)
  1166.      char *filename;
  1167.      char *database_name;
  1168.      char *typename;
  1169.      char **filenames;
  1170.      long count;
  1171.      boolean export_database;
  1172.      long tcp_port;
  1173. {
  1174.   long i;
  1175.   char hostname[120];
  1176.   struct hostent *h;
  1177.  
  1178. #ifndef THINK_C
  1179. #ifndef M_XENIX
  1180.  
  1181.   FILE *source_stream = s_fopen(filename, "w");
  1182.  
  1183.   fprintf(source_stream, "(:source \n");
  1184.   fprintf(source_stream, "   :version  3 \n");
  1185.   if(export_database){
  1186.     gethostname(hostname, 120);
  1187.     h = gethostbyname(hostname);
  1188.     if (h != NULL && 
  1189.     h->h_addr_list != NULL &&
  1190.     h->h_addr_list[0] != NULL) {
  1191.       fprintf(source_stream,
  1192.           "   :ip-address \"%d.%d.%d.%d\"\n",
  1193.           (unsigned char)h->h_addr_list[0][0], 
  1194.           (unsigned char)h->h_addr_list[0][1],
  1195.           (unsigned char)h->h_addr_list[0][2],
  1196.           (unsigned char)h->h_addr_list[0][3] );
  1197.     }
  1198.     fprintf(source_stream, "   :ip-name \"%s\"\n", hostname );
  1199.     fprintf(source_stream, "   :tcp-port %ld\n", tcp_port);
  1200.   }
  1201.   fprintf(source_stream, "   :database-name \"%s\"\n", database_name);
  1202.   fprintf(source_stream, "   :cost 0.00 \n");
  1203.   fprintf(source_stream, "   :cost-unit :free \n");
  1204.   fprintf(source_stream, "   :maintainer \"%s\"\n", 
  1205.       current_user_name());
  1206.   fprintf(source_stream, "   :description \"Server created with %s on %s by %s\n",
  1207.       VERSION, printable_time(), current_user_name());
  1208.   if(count > 0){
  1209.     fprintf(source_stream, "The files of type %s used in the index were:\n",
  1210.         typename);
  1211.     for(i = 0; i < count; i++){
  1212.       char full_path[MAX_FILENAME_LEN + 1];
  1213.       fprintf(source_stream, "   %s\n", truename(filenames[i], full_path));
  1214.     }
  1215.   }
  1216.   fprintf(source_stream, "\"\n");
  1217.   fprintf(source_stream, ")\n");      
  1218.   s_fclose(source_stream);
  1219.   
  1220. #endif /* ndef M_XENIX */
  1221. #endif /* ndef THINK_C */
  1222.  
  1223.   return(true);
  1224. }
  1225.  
  1226.  
  1227.  
  1228.  
  1229. /*****************************/
  1230. /***   Database support    ***/
  1231. /*****************************/
  1232.  
  1233. char* dictionary_filename(destination,db)
  1234. char* destination;
  1235. database* db;
  1236. {
  1237.     strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1238.     s_strncat(destination,dictionary_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1239.     return(destination);
  1240. }
  1241.  
  1242. char* document_table_filename(destination,db)
  1243. char* destination;
  1244. database* db;
  1245. {
  1246.     strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1247.     s_strncat(destination,document_table_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1248.     return(destination);
  1249. }
  1250.  
  1251. char* filename_table_filename(destination,db)
  1252. char* destination;
  1253. database* db;
  1254. {
  1255.     strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1256.     s_strncat(destination,filename_table_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1257.     return(destination);
  1258. }
  1259.  
  1260. char* headline_table_filename(destination,db)
  1261. char* destination;
  1262. database* db;
  1263. {
  1264.     strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1265.     s_strncat(destination,headline_table_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1266.     return(destination);
  1267. }
  1268. char* index_filename(destination,db)
  1269. char* destination;
  1270. database* db;
  1271. {
  1272.     strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1273.     s_strncat(destination,index_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1274.     return(destination);
  1275. }
  1276.  
  1277. char* index_filename_with_version(version,destination,db)
  1278. long version;
  1279. char* destination;
  1280. database* db;
  1281. {
  1282.   sprintf(destination, "%s%s%ld", db->database_file,
  1283.       index_ext, version);
  1284.   return(destination);
  1285. }
  1286.  
  1287.  
  1288. char* source_filename(destination,db)
  1289. char* destination;
  1290. database* db;
  1291. {
  1292.   strncpy(destination, db->database_file,MAX_FILE_NAME_LEN);
  1293.   s_strncat(destination,source_ext,MAX_FILE_NAME_LEN,MAX_FILE_NAME_LEN);
  1294.   return(destination);
  1295. }
  1296.  
  1297. char*
  1298. get_doc(destination, document_id, db, headline)
  1299. char* destination;
  1300. long document_id;
  1301. database* db;
  1302. boolean headline;
  1303. {
  1304.   document_table_entry doc_entry;
  1305.   char filename[MAX_FILE_NAME_LEN], type[100];
  1306.  
  1307.   if (read_document_table_entry(&doc_entry, document_id, db) 
  1308.       == true){
  1309.     read_filename_table_entry(doc_entry.filename_id, 
  1310.                   filename,
  1311.                   type,
  1312.                   NULL,
  1313.                   db);
  1314.     if (headline == TRUE)
  1315.       sprintf(destination, "%d %d %s, \"%s\"", 
  1316.           doc_entry.start_character, doc_entry.end_character,
  1317.           filename,
  1318.           read_headline_table_entry(doc_entry.headline_id,db));
  1319.     else
  1320.       sprintf(destination, "%d %d %s", 
  1321.           doc_entry.start_character, doc_entry.end_character,
  1322.           filename);
  1323.     return(s_strdup(type));
  1324.   }
  1325.   else return NULL;
  1326. }
  1327.  
  1328. long next_doc(destination, docID, db)
  1329. char* destination;
  1330. char* docID;
  1331. database* db;
  1332. {
  1333.   long i, start, end;
  1334.   char doc[MAX_FILE_NAME_LEN+50], fn[MAX_FILE_NAME_LEN];
  1335.   char *type, *loc;
  1336.  
  1337.   for(i = 0; i < db->doc_table_allocated_entries; i++) {
  1338.     if (get_doc(doc, i, db, FALSE) != NULL) {
  1339.       if (strcmp(doc, docID) == 0) {
  1340.     type = get_doc(doc, i+1, db, TRUE);
  1341.     sscanf(doc, "%d %d %s", &start, &end, fn);
  1342.     if((loc = strstr(doc, ",")) == NULL) return -1;
  1343.     fn[loc-doc] = 0;
  1344.     sprintf(destination, "%s, %s", doc, type);
  1345.     if( end != 0)
  1346.       return(end-start);
  1347.     else {    
  1348.       /* whole file, find file length from the file */
  1349.       long size;
  1350.       FILE* file = NULL;
  1351.       if (((file = s_fopen(fn, "r")) != NULL) &&
  1352.           (fseek(file, 0L, SEEK_END) == 0)  &&
  1353.           ((size = ftell(file)) != -1)) {
  1354.         s_fclose(file);
  1355.         return(size);    /* we are done, bytes is set */
  1356.       }
  1357.       else {
  1358.         s_fclose(file);
  1359.         return(-1);        /* something went wrong with the file */
  1360.       }
  1361.     }
  1362.       }
  1363.     }
  1364.   }
  1365.   return -1;
  1366. }
  1367.  
  1368. long previous_doc(destination, docID, db)
  1369. char* destination;
  1370. char* docID;
  1371. database* db;
  1372. {
  1373.   long i, start, end;
  1374.   char doc[MAX_FILE_NAME_LEN+50], fn[MAX_FILE_NAME_LEN];
  1375.   char *type, *loc;
  1376.  
  1377.   for(i = 0; i < db->doc_table_allocated_entries; i++) {
  1378.     if (get_doc(doc, i, db, FALSE) != NULL) {
  1379.       if (strcmp(doc, docID) == 0) {
  1380.     if (i != 0) {
  1381.       type = get_doc(doc, i-1, db, TRUE);
  1382.       sscanf(doc, "%d %d %s", &start, &end, fn);
  1383.       if((loc = strstr(doc, ",")) == NULL) return -1;
  1384.       fn[loc-doc] = 0;
  1385.       sprintf(destination, "%s, %s", doc, type);
  1386.       if( end != 0)
  1387.         return(end-start);
  1388.       else {    
  1389.         /* whole file, find file length from the file */
  1390.         long size;
  1391.         FILE* file = NULL;
  1392.         if (((file = s_fopen(fn, "r")) != NULL) &&
  1393.         (fseek(file, 0L, SEEK_END) == 0)  &&
  1394.         ((size = ftell(file)) != -1)) {
  1395.           s_fclose(file);
  1396.           return(size);    /* we are done, bytes is set */
  1397.         }
  1398.         else {
  1399.           s_fclose(file);
  1400.           return(-1);    /* something went wrong with the file */
  1401.         }
  1402.       }
  1403.     }
  1404.       }
  1405.     }
  1406.   }
  1407.   return(-1);
  1408. }
  1409.  
  1410. long next_docid(docID, db)
  1411. char* docID;
  1412. database* db;
  1413. {
  1414.   long i;
  1415.   char doc[MAX_FILE_NAME_LEN+50];
  1416.  
  1417.   for(i = 0; i < db->doc_table_allocated_entries; i++) {
  1418.     if (get_doc(doc, i, db, FALSE) != NULL) {
  1419.       if (strcmp(doc, docID) == 0) {
  1420.     return (i+1);
  1421.       }
  1422.     }
  1423.   }
  1424.   return -1;
  1425. }
  1426.  
  1427. long previous_docid(docID, db)
  1428. char* docID;
  1429. database* db;
  1430. {
  1431.   long i;
  1432.   char doc[MAX_FILE_NAME_LEN+50];
  1433.  
  1434.   for(i = 0; i < db->doc_table_allocated_entries; i++) {
  1435.     if (get_doc(doc, i, db, FALSE) != NULL) {
  1436.       if (strcmp(doc, docID) == 0) {
  1437.     return (i-1);
  1438.       }
  1439.     }
  1440.   }
  1441.   return -1;
  1442. }
  1443.