/* gettitle.c -- Copyright 1993, 1994 Liam R. Quin. * All Rights Reserved. * This code is NOT in the public domain. * See the file COPYRIGHT for full details. */ #ifndef LINE static char *RcsId = "@(#) $Id: gettitle.c,v 1.7 96/08/14 16:55:12 lee Exp $"; #endif /* This file defines: * * char * * LQT_FIDToDocumentTitle(t_FID FID) * */ #include "globals.h" /* defines and declarations for database filenames */ #include "error.h" #include /* stderr, also for fileinfo.h */ #include #include #include #ifdef HAVE_FCNTL_H # ifdef HAVE_SYSV_FCNTL_H # include # endif # include #endif #include "fileinfo.h" /* for t_FID.h */ #include "emalloc.h" #include "lqutil.h" #include "liblqtext.h" #include "lqtrace.h" #ifdef HAVE_STDLIB_H # include #else # include #endif #ifdef HAVE_UNISTD_H #include /* for SEEK_SET, or use 0 if it's not found */ #endif #ifdef HAVE_STRING_H # include #else # include #endif /** Unix system calls that need to be declared: **/ /** C library functions that need to be declared: **/ /** lqtext library functions that need to be declared: **/ /** Functions within this file that need to be declared: **/ /** **/ static char *titleFileName = 0; static int titleFile; typedef enum { FS_NotTried, FS_Open, FS_Closed, FS_NotAvailable } t_FileState; static t_FileState FileState = FS_NotTried; static unsigned long TitleFileSize; PRIVATE int OpenTitleFile(db) t_LQTEXT_Database *db; { titleFileName = LQU_joinstr2(db->DatabaseDirectory, "/titles"); titleFile = open(titleFileName, O_RDONLY, 0); if (titleFile < 0) { if (LQT_TraceFlagsSet(LQTRACE_DEBUG)) { Error(E_WARN|E_SYS, "couldn't open document title file %s", titleFileName ); } } else { struct stat statbuf; if (fstat(titleFile, &statbuf) < 0) { Error(E_WARN|E_SYS, "couldn't fstat file \"%s\" (%d) containing Document Titles", titleFileName, titleFile ); FileState = FS_NotAvailable; (void) close(titleFile); } TitleFileSize = statbuf.st_size; } return titleFile; } /* Titles longer than MAXLINE get truncated. * Also, the binary search algorithm assumes that two successive entries * are no further apart than MAXLINE - a-little-bit bytes. */ #define MAXLINE 2048 PRIVATE char * ReadTitle(FID, Name) t_FID FID; char *Name; { static char Buffer[MAXLINE]; static t_FID lastFID = ~(t_FID) 0; static short theOffset; long LowestPossibleOffsetOfStartOfLine = 0L; long HighestPossibleOffsetOfStartOfLine; int MinimumGapBetweenLines; long ValueFound = 0; if (FID == lastFID) { return &Buffer[theOffset]; } /* the format is * FID title * where FID is an ascii hexadeciml representation, * and title is a string. * TODO: use data file to store this. */ /*** binary search ***/ /* work out how close the lines might be. Since the line we * want must contain the FID and a newline, we'll use strlen * as a guess: */ (void) sprintf(Buffer, "%ld", FID); MinimumGapBetweenLines = strlen(Buffer) + 1; /* +1 for the tab */ /* the line can't start any nearer the end of the file than * MinimumGapBetweenLines: */ HighestPossibleOffsetOfStartOfLine = TitleFileSize; for (;;) { long Guess; int BytesRead; register char *p; NextGuess: if (LowestPossibleOffsetOfStartOfLine > HighestPossibleOffsetOfStartOfLine - MinimumGapBetweenLines) { /* not found */ break; } Guess = (HighestPossibleOffsetOfStartOfLine + LowestPossibleOffsetOfStartOfLine) / 2; SeekAgain: if (LowestPossibleOffsetOfStartOfLine > TitleFileSize - MinimumGapBetweenLines) { /* not there */ break; } { static long lastGuess = -1L; static char nTries = 0; static t_FID lastFIDhere = 0; if (Guess == lastGuess && lastFIDhere == FID) { if (nTries > 2) { return Name; } else { nTries++; } } else { lastGuess = Guess; nTries = 0; lastFIDhere = FID; } } if (LQU_Elseek( E_WARN, titleFileName, "FID to document title map", titleFile, Guess, SEEK_SET /* = 0 */ ) < 0) { FileState = FS_Closed; /* but try again */ (void) close(titleFile); return Name; } BytesRead = read(titleFile, Buffer, MAXLINE); if (BytesRead <= 0) { Error(E_SYS|E_WARN, "couldn't read from document title file \"%s\" at offset %ld", titleFileName, Guess ); FileState = FS_Closed; /* but try again */ (void) close(titleFile); return Name; } ValueFound = 0; for (p = Buffer; p - Buffer < BytesRead-MinimumGapBetweenLines; p++) { if (*p == '\n' || (Guess == 0L && p == Buffer)) { char *StartOfLine; StartOfLine = p; /* also the end of the previous line */ /* got the start of a line */ if (Guess != 0L) { p++; } if (*p == '#') { /* comment ignored */ continue; } else if (!isdigit(*p)) { Error(E_WARN, "Document Title File \"%s\" has line not starting with digit or #", titleFileName ); FileState = FS_NotAvailable; (void) close(titleFile); return Name; } ValueFound = 0; while (isdigit(*p) && p - Buffer < MAXLINE) { ValueFound *= 10; ValueFound += *p - '0'; p++; } if (p - Buffer >= MAXLINE - 2) { /* we need \t and \n */ Guess += MAXLINE / 2; if (Guess >= TitleFileSize - MinimumGapBetweenLines) { /* not there */ break; } goto SeekAgain; } if (ValueFound < FID) { continue; } else if (ValueFound > FID) { HighestPossibleOffsetOfStartOfLine = Guess; goto NextGuess; } else if (ValueFound == FID) { char *Result = ++p; while (p - Buffer <= MAXLINE) { if (*p == '\n') { break; } p++; } if (*p != '\n') { if (StartOfLine >= &Buffer[2]) { Guess = Guess + (StartOfLine - Buffer); goto SeekAgain; } } *p = '\0'; lastFID = FID; theOffset = Result - Buffer; return Result; } } } if (ValueFound && ValueFound < FID) { LowestPossibleOffsetOfStartOfLine = Guess + (p - Buffer); } else if (p - Buffer >= MAXLINE) { Guess += MAXLINE / 2; goto SeekAgain; } } return Name; /* not found */ } /* * LQT_FIDToDocumentTitle * Database/Retrieval, Database/Documents * * Returns a document title (from the database titles file) * for a given FID. A binary search is used to locate a line in the * titles file which starts with the given FID, as a decimal ASCII number, * followed by a tab; the remainder of that line up to a newline or * EOF is returnd. The second (Name) argument is only used on error. * * *
  • the title on success, in a static buffer *
  • The given Name pointer on error. * * * Warns if the title file can't be opened. * * The `lqkwic' client uses this function to expand ${Title}. * */ API char * LQT_FIDToDocumentTitle(db, FID, Name) t_LQTEXT_Database *db; t_FID FID; char *Name; { switch (FileState) { case FS_NotAvailable: return Name; case FS_Closed: case FS_NotTried: if (OpenTitleFile(db) < 0) { FileState = FS_NotAvailable; return Name; } else { FileState = FS_Open; } /* fall through */ case FS_Open: default: /* fall through */ break; } /* the file is open, let's read the title */ return ReadTitle(FID, Name); }