Mercurial > noffle
diff src/database.c @ 43:2842f50feb55 noffle
[svn] * client.c, client.h, common.h, config.c, config.h, content.c, content.h,
control.c, control.h, database.c, database.h, dynamicstring.c,
dynamicstring.h, fetch.c, fetch.h, fetchlist.c, fetchlist.h, group.c,
group.h, itemlist.c, itemlist.h, lock.c, lock.h, log.c, log.h, noffle.c,
online.c, online.h, outgoing.c, outgoing.h, over.c, over.h, post.c, post.h,
protocol.c, protocol.h, pseudo.c, pseudo.h, request.c, request.h, server.c,
server.h, util.c, util.h, wildmat.c, wildmat.h: Moved files to the
subdirectory src/
* Makefile.am, acconfig.h, configure.in, docs/Makefile.am, src/Makefile.am,
Makefile.in, aclocal.m4, config.h.in, configure, install-sh, missing,
mkinstalldirs, stamp-h.in, docs/Makefile.in, src/Makefile.in: Added files.
They are used by aclocal, autoheader, autoconf and automake.
* src/config.c, src/config.h: Renamed to configfile.c and configfile.h,
because configure will generate a config.h file itself.
* src/client.c, src/content.c, src/database.c, src/fetch.c, src/fetchlist.c,
src/group.c, src/lock.c, src/noffle.c, src/online.c, src/outgoing.c,
src/over.c, src/pseudo.c, src/request.c, src/server.c, src/util.c:
Changed '#include "config.h"' to '#include "configfile.h"'.
* src/client.c, src/content.c, src/database.c, src/fetch.c, src/fetchlist.c,
src/group.c, src/lock.c, src/online.c, src/outgoing.c, src/post.c,
src/protocol.c, src/request.c, src/server.c: Files now #include <config.h>.
Added missing <stdio.h>. This removes the warnings about snprintf() not
being declared.
* Makefile: Removed. This is now generated by configure.
author | uh1763 |
---|---|
date | Fri, 05 May 2000 22:45:56 +0100 |
parents | |
children | 125d79c9e586 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/database.c Fri May 05 22:45:56 2000 +0100 @@ -0,0 +1,684 @@ +/* + database.c + + $Id: database.c 49 2000-05-05 21:45:56Z uh1763 $ + + Uses GNU gdbm library. Using Berkeley db (included in libc6) was + cumbersome. It is based on Berkeley db 1.85, which has severe bugs + (e.g. it is not recommended to delete or overwrite entries with + overflow pages). +*/ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include "database.h" +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <gdbm.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "configfile.h" +#include "itemlist.h" +#include "log.h" +#include "protocol.h" +#include "util.h" +#include "wildmat.h" + +static struct Db +{ + GDBM_FILE dbf; + + /* Start string for Xref header line: "Xref: <host>" */ + Str xrefHost; + + /* Msg Id of presently loaded article, empty if none loaded */ + Str msgId; + + /* Status of loaded article */ + int stat; /* Flags */ + time_t lastAccess; + + /* Overview of loaded article */ + Str subj; + Str from; + Str date; + Str ref; + Str xref; + size_t bytes; + size_t lines; + + /* Article text (except for overview header lines) */ + DynStr *txt; + +} db = { NULL, "(unknown)", "", 0, 0, "", "", "", "", "", 0, 0, NULL }; + +static const char * +errMsg( void ) +{ + if ( errno != 0 ) + return strerror( errno ); + return gdbm_strerror( gdbm_errno ); +} + +Bool +Db_open( void ) +{ + Str name, host; + int flags; + + ASSERT( db.dbf == NULL ); + snprintf( name, MAXCHAR, "%s/data/articles.gdbm", Cfg_spoolDir() ); + flags = GDBM_WRCREAT | GDBM_FAST; + + if ( ! ( db.dbf = gdbm_open( name, 512, flags, 0644, NULL ) ) ) + { + Log_err( "Error opening %s for r/w (%s)", name, errMsg() ); + return FALSE; + } + Log_dbg( "%s opened for r/w", name ); + + if ( db.txt == NULL ) + db.txt = new_DynStr( 5000 ); + + gethostname( host, MAXCHAR ); + snprintf( db.xrefHost, MAXCHAR, "Xref: %s", host ); + + return TRUE; +} + +void +Db_close( void ) +{ + ASSERT( db.dbf ); + Log_dbg( "Closing database" ); + gdbm_close( db.dbf ); + db.dbf = NULL; + del_DynStr( db.txt ); + db.txt = NULL; + Utl_cpyStr( db.msgId, "" ); +} + +static Bool +loadArt( const char *msgId ) +{ + static void *dptr = NULL; + + datum key, val; + Str t = ""; + const char *p; + + ASSERT( db.dbf ); + + if ( strcmp( msgId, db.msgId ) == 0 ) + return TRUE; + + key.dptr = (void *)msgId; + key.dsize = strlen( msgId ) + 1; + if ( dptr != NULL ) + { + free( dptr ); + dptr = NULL; + } + val = gdbm_fetch( db.dbf, key ); + dptr = val.dptr; + if ( dptr == NULL ) + { + Log_dbg( "database.c loadArt: gdbm_fetch found no entry" ); + return FALSE; + } + + Utl_cpyStr( db.msgId, msgId ); + p = Utl_getLn( t, (char *)dptr ); + if ( ! p || sscanf( t, "%x", &db.stat ) != 1 ) + { + Log_err( "Entry in database '%s' is corrupt (status)", msgId ); + return FALSE; + } + p = Utl_getLn( t, p ); + if ( ! p || sscanf( t, "%lu", &db.lastAccess ) != 1 ) + { + Log_err( "Entry in database '%s' is corrupt (lastAccess)", msgId ); + return FALSE; + } + p = Utl_getLn( db.subj, p ); + p = Utl_getLn( db.from, p ); + p = Utl_getLn( db.date, p ); + p = Utl_getLn( db.ref, p ); + p = Utl_getLn( db.xref, p ); + if ( ! p ) + { + Log_err( "Entry in database '%s' is corrupt (overview)", msgId ); + return FALSE; + } + p = Utl_getLn( t, p ); + if ( ! p || sscanf( t, "%u", &db.bytes ) != 1 ) + { + Log_err( "Entry in database '%s' is corrupt (bytes)", msgId ); + return FALSE; + } + p = Utl_getLn( t, p ); + if ( ! p || sscanf( t, "%u", &db.lines ) != 1 ) + { + Log_err( "Entry in database '%s' is corrupt (lines)", msgId ); + return FALSE; + } + DynStr_clear( db.txt ); + DynStr_app( db.txt, p ); + return TRUE; +} + +static Bool +saveArt( void ) +{ + DynStr *s; + Str t = ""; + datum key, val; + + if ( strcmp( db.msgId, "" ) == 0 ) + return FALSE; + s = new_DynStr( 5000 ); + snprintf( t, MAXCHAR, "%x", db.stat ); + DynStr_appLn( s, t ); + snprintf( t, MAXCHAR, "%lu", db.lastAccess ); + DynStr_appLn( s, t ); + DynStr_appLn( s, db.subj ); + DynStr_appLn( s, db.from ); + DynStr_appLn( s, db.date ); + DynStr_appLn( s, db.ref ); + DynStr_appLn( s, db.xref ); + snprintf( t, MAXCHAR, "%u", db.bytes ); + DynStr_appLn( s, t ); + snprintf( t, MAXCHAR, "%u", db.lines ); + DynStr_appLn( s, t ); + DynStr_appDynStr( s, db.txt ); + + key.dptr = (void *)db.msgId; + key.dsize = strlen( db.msgId ) + 1; + val.dptr = (void *)DynStr_str( s ); + val.dsize = DynStr_len( s ) + 1; + if ( gdbm_store( db.dbf, key, val, GDBM_REPLACE ) != 0 ) + { + Log_err( "Could not store %s in database (%s)", errMsg() ); + return FALSE; + } + + del_DynStr( s ); + return TRUE; +} + +Bool +Db_prepareEntry( const Over *ov, const char *grp, int numb ) +{ + const char *msgId; + + ASSERT( db.dbf ); + ASSERT( ov ); + ASSERT( grp ); + + msgId = Ov_msgId( ov ); + Log_dbg( "Preparing entry %s", msgId ); + if ( Db_contains( msgId ) ) + Log_err( "Preparing article twice: %s", msgId ); + + db.stat = DB_NOT_DOWNLOADED; + db.lastAccess = time( NULL ); + + Utl_cpyStr( db.msgId, msgId ); + Utl_cpyStr( db.subj, Ov_subj( ov ) ); + Utl_cpyStr( db.from, Ov_from( ov ) ); + Utl_cpyStr( db.date, Ov_date( ov ) ); + Utl_cpyStr( db.ref, Ov_ref( ov ) ); + snprintf( db.xref, MAXCHAR, "%s:%i", grp, numb ); + db.bytes = Ov_bytes( ov ); + db.lines = Ov_lines( ov ); + + DynStr_clear( db.txt ); + + return saveArt(); +} + +Bool +Db_storeArt( const char *msgId, const char *artTxt ) +{ + Str line, lineEx, field, value; + const char *startPos; + + ASSERT( db.dbf ); + + Log_dbg( "Store article %s", msgId ); + if ( ! loadArt( msgId ) ) + { + Log_err( "Cannot find info about '%s' in database", msgId ); + return FALSE; + } + if ( ! ( db.stat & DB_NOT_DOWNLOADED ) ) + { + Log_err( "Trying to store already retrieved article '%s'", msgId ); + return FALSE; + } + db.stat &= ~DB_NOT_DOWNLOADED; + db.stat &= ~DB_RETRIEVING_FAILED; + db.lastAccess = time( NULL ); + + DynStr_clear( db.txt ); + + /* Read header */ + startPos = artTxt; + while ( TRUE ) + { + artTxt = Utl_getHeaderLn( lineEx, artTxt ); + if ( lineEx[ 0 ] == '\0' ) + { + DynStr_appLn( db.txt, lineEx ); + break; + } + /* Remove fields already in overview and handle x-noffle + headers correctly in case of cascading NOFFLEs */ + if ( Prt_getField( field, value, lineEx ) ) + { + if ( strcmp( field, "x-noffle-status" ) == 0 ) + { + if ( strstr( value, "NOT_DOWNLOADED" ) != 0 ) + db.stat |= DB_NOT_DOWNLOADED; + } + else if ( strcmp( field, "message-id" ) != 0 + && strcmp( field, "xref" ) != 0 + && strcmp( field, "references" ) != 0 + && strcmp( field, "subject" ) != 0 + && strcmp( field, "from" ) != 0 + && strcmp( field, "date" ) != 0 + && strcmp( field, "bytes" ) != 0 + && strcmp( field, "lines" ) != 0 + && strcmp( field, "x-noffle-lastaccess" ) != 0 ) + DynStr_appLn( db.txt, lineEx ); + } + } + + /* Read body */ + while ( ( artTxt = Utl_getLn( line, artTxt ) ) ) + if ( ! ( db.stat & DB_NOT_DOWNLOADED ) ) + DynStr_appLn( db.txt, line ); + + return saveArt(); +} + +void +Db_setStat( const char *msgId, int stat ) +{ + if ( loadArt( msgId ) ) + { + db.stat = stat; + saveArt(); + } +} + +void +Db_updateLastAccess( const char *msgId ) +{ + if ( loadArt( msgId ) ) + { + db.lastAccess = time( NULL ); + saveArt(); + } +} + +void +Db_setXref( const char *msgId, const char *xref ) +{ + if ( loadArt( msgId ) ) + { + Utl_cpyStr( db.xref, xref ); + saveArt(); + } +} + +/* Search best position for breaking a line */ +static const char * +searchBreakPos( const char *line, int wantedLength ) +{ + const char *lastSpace = NULL; + Bool lastWasSpace = FALSE; + int len = 0; + + while ( *line != '\0' ) + { + if ( isspace( *line ) ) + { + if ( len > wantedLength && lastSpace != NULL ) + return lastSpace; + if ( ! lastWasSpace ) + lastSpace = line; + lastWasSpace = TRUE; + } + else + lastWasSpace = FALSE; + ++len; + ++line; + } + if ( len > wantedLength && lastSpace != NULL ) + return lastSpace; + return line; +} + +/* Append header line by breaking long line into multiple lines */ +static void +appendLongHeader( DynStr *target, const char *field, const char *value ) +{ + const int wantedLength = 78; + const char *breakPos, *old; + int len; + + len = strlen( field ); + DynStr_appN( target, field, len ); + DynStr_appN( target, " ", 1 ); + old = value; + while ( isspace( *old ) ) + ++old; + breakPos = searchBreakPos( old, wantedLength - len - 1 ); + DynStr_appN( target, old, breakPos - old ); + if ( *breakPos == '\0' ) + { + DynStr_appN( target, "\n", 1 ); + return; + } + DynStr_appN( target, "\n ", 2 ); + while ( TRUE ) + { + old = breakPos; + while ( isspace( *old ) ) + ++old; + breakPos = searchBreakPos( old, wantedLength - 1 ); + DynStr_appN( target, old, breakPos - old ); + if ( *breakPos == '\0' ) + { + DynStr_appN( target, "\n", 1 ); + return; + } + DynStr_appN( target, "\n ", 2 ); + } +} + +const char * +Db_header( const char *msgId ) +{ + static DynStr *s = NULL; + + Str date, t; + int stat; + const char *p; + + if ( s == NULL ) + s = new_DynStr( 5000 ); + else + DynStr_clear( s ); + ASSERT( db.dbf ); + if ( ! loadArt( msgId ) ) + return NULL; + strftime( date, MAXCHAR, "%Y-%m-%d %H:%M:%S", + localtime( &db.lastAccess ) ); + stat = db.stat; + snprintf( t, MAXCHAR, + "Message-ID: %s\n" + "X-NOFFLE-Status:%s%s%s\n" + "X-NOFFLE-LastAccess: %s\n", + msgId, + stat & DB_INTERESTING ? " INTERESTING" : "", + stat & DB_NOT_DOWNLOADED ? " NOT_DOWNLOADED" : "", + stat & DB_RETRIEVING_FAILED ? " RETRIEVING_FAILED" : "", + date ); + DynStr_app( s, t ); + appendLongHeader( s, "Subject:", db.subj ); + appendLongHeader( s, "From:", db.from ); + appendLongHeader( s, "Date:", db.date ); + appendLongHeader( s, "References:", db.ref ); + DynStr_app( s, "Bytes: " ); + snprintf( t, MAXCHAR, "%u", db.bytes ); + DynStr_appLn( s, t ); + DynStr_app( s, "Lines: " ); + snprintf( t, MAXCHAR, "%u", db.lines ); + DynStr_appLn( s, t ); + appendLongHeader( s, db.xrefHost, db.xref ); + p = strstr( DynStr_str( db.txt ), "\n\n" ); + if ( ! p ) + DynStr_appDynStr( s, db.txt ); + else + DynStr_appN( s, DynStr_str( db.txt ), p - DynStr_str( db.txt ) + 1 ); + return DynStr_str( s ); +} + +const char * +Db_body( const char *msgId ) +{ + const char *p; + + if ( ! loadArt( msgId ) ) + return ""; + p = strstr( DynStr_str( db.txt ), "\n\n" ); + if ( ! p ) + return ""; + return ( p + 2 ); +} + +int +Db_stat( const char *msgId ) +{ + if ( ! loadArt( msgId ) ) + return 0; + return db.stat; +} + +time_t +Db_lastAccess( const char *msgId ) +{ + if ( ! loadArt( msgId ) ) + return -1; + return db.lastAccess; +} + +const char * +Db_ref( const char *msgId ) +{ + if ( ! loadArt( msgId ) ) + return ""; + return db.ref; +} + +const char * +Db_xref( const char *msgId ) +{ + if ( ! loadArt( msgId ) ) + return ""; + return db.xref; +} + +const char * +Db_from( const char *msgId ) +{ + if ( ! loadArt( msgId ) ) + return ""; + return db.from; +} + +const char * +Db_date( const char *msgId ) +{ + if ( ! loadArt( msgId ) ) + return ""; + return db.date; +} + +Bool +Db_contains( const char *msgId ) +{ + datum key; + + ASSERT( db.dbf ); + if ( strcmp( msgId, db.msgId ) == 0 ) + return TRUE; + key.dptr = (void*)msgId; + key.dsize = strlen( msgId ) + 1; + return gdbm_exists( db.dbf, key ); +} + +void +Db_delete( const char *msgId ) +{ + datum key; + + ASSERT( db.dbf ); + if ( strcmp( msgId, db.msgId ) == 0 ) + db.msgId[ 0 ] = '\0'; + key.dptr = (void*)msgId; + key.dsize = strlen( msgId ) + 1; + gdbm_delete( db.dbf, key ); +} + +static datum cursor = { NULL, 0 }; + +Bool +Db_first( const char** msgId ) +{ + ASSERT( db.dbf ); + if ( cursor.dptr != NULL ) + { + free( cursor.dptr ); + cursor.dptr = NULL; + } + cursor = gdbm_firstkey( db.dbf ); + *msgId = cursor.dptr; + return ( cursor.dptr != NULL ); +} + +Bool +Db_next( const char** msgId ) +{ + void *oldDptr = cursor.dptr; + + ASSERT( db.dbf ); + if ( cursor.dptr == NULL ) + return FALSE; + cursor = gdbm_nextkey( db.dbf, cursor ); + free( oldDptr ); + *msgId = cursor.dptr; + return ( cursor.dptr != NULL ); +} + +static int +calcExpireDays( const char *msgId ) +{ + const char *xref; + ItemList *refs; + const char *ref; + int res; + + xref = Db_xref( msgId ); + if ( xref[ 0 ] == '\0' ) + return -1; + + res = -1; + refs = new_Itl( xref, " :" ); + for ( ref = Itl_first( refs ); ref != NULL; ref = Itl_next( refs ) ) + { + Str pattern; + int days; + + Cfg_beginExpireEnum(); + while ( ( days = Cfg_nextExpire( pattern ) ) != -1 ) + if ( Wld_match( ref, pattern ) + && ( ( days > res && res != 0 ) || + days == 0 ) ) + { + res = days; + Log_dbg ( "Custom expiry %d for %s in group %s", + days, msgId, ref ); + break; + } + + Itl_next( refs ); /* Throw away group number */ + } + + if ( res == -1 ) + res = Cfg_expire(); + return res; +} + +Bool +Db_expire( void ) +{ + int cntDel, cntLeft, flags, expDays; + time_t nowTime, lastAccess; + const char *msgId; + Str name, tmpName; + GDBM_FILE tmpDbf; + datum key, val; + + if ( ! Db_open() ) + return FALSE; + snprintf( name, MAXCHAR, "%s/data/articles.gdbm", Cfg_spoolDir() ); + snprintf( tmpName, MAXCHAR, "%s/data/articles.gdbm.new", Cfg_spoolDir() ); + flags = GDBM_NEWDB | GDBM_FAST; + if ( ! ( tmpDbf = gdbm_open( tmpName, 512, flags, 0644, NULL ) ) ) + { + Log_err( "Error opening %s for read/write (%s)", errMsg() ); + Db_close(); + return FALSE; + } + Log_inf( "Expiring articles" ); + cntDel = 0; + cntLeft = 0; + nowTime = time( NULL ); + if ( Db_first( &msgId ) ) + do + { + expDays = calcExpireDays( msgId ); + lastAccess = Db_lastAccess( msgId ); + if ( expDays == -1 ) + Log_err( "Internal error: Failed expiry calculation on %s", + msgId ); + else if ( lastAccess == -1 ) + Log_err( "Internal error: Getting lastAccess of %s failed", + msgId ); + else if ( expDays > 0 + && difftime( nowTime, lastAccess ) > + ( (double) expDays * 24 * 3600 ) ) + { +#ifdef DEBUG + Str last, now; + + Utl_cpyStr( last, ctime( &lastAccess ) ); + last[ strlen( last ) - 1 ] = '\0'; + Utl_cpyStr( now, ctime( &nowTime ) ); + last[ strlen( now ) - 1 ] = '\0'; + Log_dbg( "Expiring %s: last access %s, time now %s", + msgId, last, now ); +#endif + ++cntDel; + } + else + { + ++cntLeft; + key.dptr = (void *)msgId; + key.dsize = strlen( msgId ) + 1; + + val = gdbm_fetch( db.dbf, key ); + if ( val.dptr != NULL ) + { + if ( gdbm_store( tmpDbf, key, val, GDBM_INSERT ) != 0 ) + Log_err( "Could not store %s in new database (%s)", + errMsg() ); + free( val.dptr ); + } + } + } + while ( Db_next( &msgId ) ); + Log_inf( "%lu articles deleted, %lu left", cntDel, cntLeft ); + gdbm_close( tmpDbf ); + Db_close(); + rename( tmpName, name ); + return TRUE; +}