Mercurial > noffle
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 42:2467ff423c15 | 43:2842f50feb55 |
|---|---|
| 1 /* | |
| 2 database.c | |
| 3 | |
| 4 $Id: database.c 49 2000-05-05 21:45:56Z uh1763 $ | |
| 5 | |
| 6 Uses GNU gdbm library. Using Berkeley db (included in libc6) was | |
| 7 cumbersome. It is based on Berkeley db 1.85, which has severe bugs | |
| 8 (e.g. it is not recommended to delete or overwrite entries with | |
| 9 overflow pages). | |
| 10 */ | |
| 11 | |
| 12 #if HAVE_CONFIG_H | |
| 13 #include <config.h> | |
| 14 #endif | |
| 15 | |
| 16 #include <stdio.h> | |
| 17 #include "database.h" | |
| 18 #include <ctype.h> | |
| 19 #include <errno.h> | |
| 20 #include <fcntl.h> | |
| 21 #include <gdbm.h> | |
| 22 #include <unistd.h> | |
| 23 #include <sys/types.h> | |
| 24 #include <sys/stat.h> | |
| 25 #include "configfile.h" | |
| 26 #include "itemlist.h" | |
| 27 #include "log.h" | |
| 28 #include "protocol.h" | |
| 29 #include "util.h" | |
| 30 #include "wildmat.h" | |
| 31 | |
| 32 static struct Db | |
| 33 { | |
| 34 GDBM_FILE dbf; | |
| 35 | |
| 36 /* Start string for Xref header line: "Xref: <host>" */ | |
| 37 Str xrefHost; | |
| 38 | |
| 39 /* Msg Id of presently loaded article, empty if none loaded */ | |
| 40 Str msgId; | |
| 41 | |
| 42 /* Status of loaded article */ | |
| 43 int stat; /* Flags */ | |
| 44 time_t lastAccess; | |
| 45 | |
| 46 /* Overview of loaded article */ | |
| 47 Str subj; | |
| 48 Str from; | |
| 49 Str date; | |
| 50 Str ref; | |
| 51 Str xref; | |
| 52 size_t bytes; | |
| 53 size_t lines; | |
| 54 | |
| 55 /* Article text (except for overview header lines) */ | |
| 56 DynStr *txt; | |
| 57 | |
| 58 } db = { NULL, "(unknown)", "", 0, 0, "", "", "", "", "", 0, 0, NULL }; | |
| 59 | |
| 60 static const char * | |
| 61 errMsg( void ) | |
| 62 { | |
| 63 if ( errno != 0 ) | |
| 64 return strerror( errno ); | |
| 65 return gdbm_strerror( gdbm_errno ); | |
| 66 } | |
| 67 | |
| 68 Bool | |
| 69 Db_open( void ) | |
| 70 { | |
| 71 Str name, host; | |
| 72 int flags; | |
| 73 | |
| 74 ASSERT( db.dbf == NULL ); | |
| 75 snprintf( name, MAXCHAR, "%s/data/articles.gdbm", Cfg_spoolDir() ); | |
| 76 flags = GDBM_WRCREAT | GDBM_FAST; | |
| 77 | |
| 78 if ( ! ( db.dbf = gdbm_open( name, 512, flags, 0644, NULL ) ) ) | |
| 79 { | |
| 80 Log_err( "Error opening %s for r/w (%s)", name, errMsg() ); | |
| 81 return FALSE; | |
| 82 } | |
| 83 Log_dbg( "%s opened for r/w", name ); | |
| 84 | |
| 85 if ( db.txt == NULL ) | |
| 86 db.txt = new_DynStr( 5000 ); | |
| 87 | |
| 88 gethostname( host, MAXCHAR ); | |
| 89 snprintf( db.xrefHost, MAXCHAR, "Xref: %s", host ); | |
| 90 | |
| 91 return TRUE; | |
| 92 } | |
| 93 | |
| 94 void | |
| 95 Db_close( void ) | |
| 96 { | |
| 97 ASSERT( db.dbf ); | |
| 98 Log_dbg( "Closing database" ); | |
| 99 gdbm_close( db.dbf ); | |
| 100 db.dbf = NULL; | |
| 101 del_DynStr( db.txt ); | |
| 102 db.txt = NULL; | |
| 103 Utl_cpyStr( db.msgId, "" ); | |
| 104 } | |
| 105 | |
| 106 static Bool | |
| 107 loadArt( const char *msgId ) | |
| 108 { | |
| 109 static void *dptr = NULL; | |
| 110 | |
| 111 datum key, val; | |
| 112 Str t = ""; | |
| 113 const char *p; | |
| 114 | |
| 115 ASSERT( db.dbf ); | |
| 116 | |
| 117 if ( strcmp( msgId, db.msgId ) == 0 ) | |
| 118 return TRUE; | |
| 119 | |
| 120 key.dptr = (void *)msgId; | |
| 121 key.dsize = strlen( msgId ) + 1; | |
| 122 if ( dptr != NULL ) | |
| 123 { | |
| 124 free( dptr ); | |
| 125 dptr = NULL; | |
| 126 } | |
| 127 val = gdbm_fetch( db.dbf, key ); | |
| 128 dptr = val.dptr; | |
| 129 if ( dptr == NULL ) | |
| 130 { | |
| 131 Log_dbg( "database.c loadArt: gdbm_fetch found no entry" ); | |
| 132 return FALSE; | |
| 133 } | |
| 134 | |
| 135 Utl_cpyStr( db.msgId, msgId ); | |
| 136 p = Utl_getLn( t, (char *)dptr ); | |
| 137 if ( ! p || sscanf( t, "%x", &db.stat ) != 1 ) | |
| 138 { | |
| 139 Log_err( "Entry in database '%s' is corrupt (status)", msgId ); | |
| 140 return FALSE; | |
| 141 } | |
| 142 p = Utl_getLn( t, p ); | |
| 143 if ( ! p || sscanf( t, "%lu", &db.lastAccess ) != 1 ) | |
| 144 { | |
| 145 Log_err( "Entry in database '%s' is corrupt (lastAccess)", msgId ); | |
| 146 return FALSE; | |
| 147 } | |
| 148 p = Utl_getLn( db.subj, p ); | |
| 149 p = Utl_getLn( db.from, p ); | |
| 150 p = Utl_getLn( db.date, p ); | |
| 151 p = Utl_getLn( db.ref, p ); | |
| 152 p = Utl_getLn( db.xref, p ); | |
| 153 if ( ! p ) | |
| 154 { | |
| 155 Log_err( "Entry in database '%s' is corrupt (overview)", msgId ); | |
| 156 return FALSE; | |
| 157 } | |
| 158 p = Utl_getLn( t, p ); | |
| 159 if ( ! p || sscanf( t, "%u", &db.bytes ) != 1 ) | |
| 160 { | |
| 161 Log_err( "Entry in database '%s' is corrupt (bytes)", msgId ); | |
| 162 return FALSE; | |
| 163 } | |
| 164 p = Utl_getLn( t, p ); | |
| 165 if ( ! p || sscanf( t, "%u", &db.lines ) != 1 ) | |
| 166 { | |
| 167 Log_err( "Entry in database '%s' is corrupt (lines)", msgId ); | |
| 168 return FALSE; | |
| 169 } | |
| 170 DynStr_clear( db.txt ); | |
| 171 DynStr_app( db.txt, p ); | |
| 172 return TRUE; | |
| 173 } | |
| 174 | |
| 175 static Bool | |
| 176 saveArt( void ) | |
| 177 { | |
| 178 DynStr *s; | |
| 179 Str t = ""; | |
| 180 datum key, val; | |
| 181 | |
| 182 if ( strcmp( db.msgId, "" ) == 0 ) | |
| 183 return FALSE; | |
| 184 s = new_DynStr( 5000 ); | |
| 185 snprintf( t, MAXCHAR, "%x", db.stat ); | |
| 186 DynStr_appLn( s, t ); | |
| 187 snprintf( t, MAXCHAR, "%lu", db.lastAccess ); | |
| 188 DynStr_appLn( s, t ); | |
| 189 DynStr_appLn( s, db.subj ); | |
| 190 DynStr_appLn( s, db.from ); | |
| 191 DynStr_appLn( s, db.date ); | |
| 192 DynStr_appLn( s, db.ref ); | |
| 193 DynStr_appLn( s, db.xref ); | |
| 194 snprintf( t, MAXCHAR, "%u", db.bytes ); | |
| 195 DynStr_appLn( s, t ); | |
| 196 snprintf( t, MAXCHAR, "%u", db.lines ); | |
| 197 DynStr_appLn( s, t ); | |
| 198 DynStr_appDynStr( s, db.txt ); | |
| 199 | |
| 200 key.dptr = (void *)db.msgId; | |
| 201 key.dsize = strlen( db.msgId ) + 1; | |
| 202 val.dptr = (void *)DynStr_str( s ); | |
| 203 val.dsize = DynStr_len( s ) + 1; | |
| 204 if ( gdbm_store( db.dbf, key, val, GDBM_REPLACE ) != 0 ) | |
| 205 { | |
| 206 Log_err( "Could not store %s in database (%s)", errMsg() ); | |
| 207 return FALSE; | |
| 208 } | |
| 209 | |
| 210 del_DynStr( s ); | |
| 211 return TRUE; | |
| 212 } | |
| 213 | |
| 214 Bool | |
| 215 Db_prepareEntry( const Over *ov, const char *grp, int numb ) | |
| 216 { | |
| 217 const char *msgId; | |
| 218 | |
| 219 ASSERT( db.dbf ); | |
| 220 ASSERT( ov ); | |
| 221 ASSERT( grp ); | |
| 222 | |
| 223 msgId = Ov_msgId( ov ); | |
| 224 Log_dbg( "Preparing entry %s", msgId ); | |
| 225 if ( Db_contains( msgId ) ) | |
| 226 Log_err( "Preparing article twice: %s", msgId ); | |
| 227 | |
| 228 db.stat = DB_NOT_DOWNLOADED; | |
| 229 db.lastAccess = time( NULL ); | |
| 230 | |
| 231 Utl_cpyStr( db.msgId, msgId ); | |
| 232 Utl_cpyStr( db.subj, Ov_subj( ov ) ); | |
| 233 Utl_cpyStr( db.from, Ov_from( ov ) ); | |
| 234 Utl_cpyStr( db.date, Ov_date( ov ) ); | |
| 235 Utl_cpyStr( db.ref, Ov_ref( ov ) ); | |
| 236 snprintf( db.xref, MAXCHAR, "%s:%i", grp, numb ); | |
| 237 db.bytes = Ov_bytes( ov ); | |
| 238 db.lines = Ov_lines( ov ); | |
| 239 | |
| 240 DynStr_clear( db.txt ); | |
| 241 | |
| 242 return saveArt(); | |
| 243 } | |
| 244 | |
| 245 Bool | |
| 246 Db_storeArt( const char *msgId, const char *artTxt ) | |
| 247 { | |
| 248 Str line, lineEx, field, value; | |
| 249 const char *startPos; | |
| 250 | |
| 251 ASSERT( db.dbf ); | |
| 252 | |
| 253 Log_dbg( "Store article %s", msgId ); | |
| 254 if ( ! loadArt( msgId ) ) | |
| 255 { | |
| 256 Log_err( "Cannot find info about '%s' in database", msgId ); | |
| 257 return FALSE; | |
| 258 } | |
| 259 if ( ! ( db.stat & DB_NOT_DOWNLOADED ) ) | |
| 260 { | |
| 261 Log_err( "Trying to store already retrieved article '%s'", msgId ); | |
| 262 return FALSE; | |
| 263 } | |
| 264 db.stat &= ~DB_NOT_DOWNLOADED; | |
| 265 db.stat &= ~DB_RETRIEVING_FAILED; | |
| 266 db.lastAccess = time( NULL ); | |
| 267 | |
| 268 DynStr_clear( db.txt ); | |
| 269 | |
| 270 /* Read header */ | |
| 271 startPos = artTxt; | |
| 272 while ( TRUE ) | |
| 273 { | |
| 274 artTxt = Utl_getHeaderLn( lineEx, artTxt ); | |
| 275 if ( lineEx[ 0 ] == '\0' ) | |
| 276 { | |
| 277 DynStr_appLn( db.txt, lineEx ); | |
| 278 break; | |
| 279 } | |
| 280 /* Remove fields already in overview and handle x-noffle | |
| 281 headers correctly in case of cascading NOFFLEs */ | |
| 282 if ( Prt_getField( field, value, lineEx ) ) | |
| 283 { | |
| 284 if ( strcmp( field, "x-noffle-status" ) == 0 ) | |
| 285 { | |
| 286 if ( strstr( value, "NOT_DOWNLOADED" ) != 0 ) | |
| 287 db.stat |= DB_NOT_DOWNLOADED; | |
| 288 } | |
| 289 else if ( strcmp( field, "message-id" ) != 0 | |
| 290 && strcmp( field, "xref" ) != 0 | |
| 291 && strcmp( field, "references" ) != 0 | |
| 292 && strcmp( field, "subject" ) != 0 | |
| 293 && strcmp( field, "from" ) != 0 | |
| 294 && strcmp( field, "date" ) != 0 | |
| 295 && strcmp( field, "bytes" ) != 0 | |
| 296 && strcmp( field, "lines" ) != 0 | |
| 297 && strcmp( field, "x-noffle-lastaccess" ) != 0 ) | |
| 298 DynStr_appLn( db.txt, lineEx ); | |
| 299 } | |
| 300 } | |
| 301 | |
| 302 /* Read body */ | |
| 303 while ( ( artTxt = Utl_getLn( line, artTxt ) ) ) | |
| 304 if ( ! ( db.stat & DB_NOT_DOWNLOADED ) ) | |
| 305 DynStr_appLn( db.txt, line ); | |
| 306 | |
| 307 return saveArt(); | |
| 308 } | |
| 309 | |
| 310 void | |
| 311 Db_setStat( const char *msgId, int stat ) | |
| 312 { | |
| 313 if ( loadArt( msgId ) ) | |
| 314 { | |
| 315 db.stat = stat; | |
| 316 saveArt(); | |
| 317 } | |
| 318 } | |
| 319 | |
| 320 void | |
| 321 Db_updateLastAccess( const char *msgId ) | |
| 322 { | |
| 323 if ( loadArt( msgId ) ) | |
| 324 { | |
| 325 db.lastAccess = time( NULL ); | |
| 326 saveArt(); | |
| 327 } | |
| 328 } | |
| 329 | |
| 330 void | |
| 331 Db_setXref( const char *msgId, const char *xref ) | |
| 332 { | |
| 333 if ( loadArt( msgId ) ) | |
| 334 { | |
| 335 Utl_cpyStr( db.xref, xref ); | |
| 336 saveArt(); | |
| 337 } | |
| 338 } | |
| 339 | |
| 340 /* Search best position for breaking a line */ | |
| 341 static const char * | |
| 342 searchBreakPos( const char *line, int wantedLength ) | |
| 343 { | |
| 344 const char *lastSpace = NULL; | |
| 345 Bool lastWasSpace = FALSE; | |
| 346 int len = 0; | |
| 347 | |
| 348 while ( *line != '\0' ) | |
| 349 { | |
| 350 if ( isspace( *line ) ) | |
| 351 { | |
| 352 if ( len > wantedLength && lastSpace != NULL ) | |
| 353 return lastSpace; | |
| 354 if ( ! lastWasSpace ) | |
| 355 lastSpace = line; | |
| 356 lastWasSpace = TRUE; | |
| 357 } | |
| 358 else | |
| 359 lastWasSpace = FALSE; | |
| 360 ++len; | |
| 361 ++line; | |
| 362 } | |
| 363 if ( len > wantedLength && lastSpace != NULL ) | |
| 364 return lastSpace; | |
| 365 return line; | |
| 366 } | |
| 367 | |
| 368 /* Append header line by breaking long line into multiple lines */ | |
| 369 static void | |
| 370 appendLongHeader( DynStr *target, const char *field, const char *value ) | |
| 371 { | |
| 372 const int wantedLength = 78; | |
| 373 const char *breakPos, *old; | |
| 374 int len; | |
| 375 | |
| 376 len = strlen( field ); | |
| 377 DynStr_appN( target, field, len ); | |
| 378 DynStr_appN( target, " ", 1 ); | |
| 379 old = value; | |
| 380 while ( isspace( *old ) ) | |
| 381 ++old; | |
| 382 breakPos = searchBreakPos( old, wantedLength - len - 1 ); | |
| 383 DynStr_appN( target, old, breakPos - old ); | |
| 384 if ( *breakPos == '\0' ) | |
| 385 { | |
| 386 DynStr_appN( target, "\n", 1 ); | |
| 387 return; | |
| 388 } | |
| 389 DynStr_appN( target, "\n ", 2 ); | |
| 390 while ( TRUE ) | |
| 391 { | |
| 392 old = breakPos; | |
| 393 while ( isspace( *old ) ) | |
| 394 ++old; | |
| 395 breakPos = searchBreakPos( old, wantedLength - 1 ); | |
| 396 DynStr_appN( target, old, breakPos - old ); | |
| 397 if ( *breakPos == '\0' ) | |
| 398 { | |
| 399 DynStr_appN( target, "\n", 1 ); | |
| 400 return; | |
| 401 } | |
| 402 DynStr_appN( target, "\n ", 2 ); | |
| 403 } | |
| 404 } | |
| 405 | |
| 406 const char * | |
| 407 Db_header( const char *msgId ) | |
| 408 { | |
| 409 static DynStr *s = NULL; | |
| 410 | |
| 411 Str date, t; | |
| 412 int stat; | |
| 413 const char *p; | |
| 414 | |
| 415 if ( s == NULL ) | |
| 416 s = new_DynStr( 5000 ); | |
| 417 else | |
| 418 DynStr_clear( s ); | |
| 419 ASSERT( db.dbf ); | |
| 420 if ( ! loadArt( msgId ) ) | |
| 421 return NULL; | |
| 422 strftime( date, MAXCHAR, "%Y-%m-%d %H:%M:%S", | |
| 423 localtime( &db.lastAccess ) ); | |
| 424 stat = db.stat; | |
| 425 snprintf( t, MAXCHAR, | |
| 426 "Message-ID: %s\n" | |
| 427 "X-NOFFLE-Status:%s%s%s\n" | |
| 428 "X-NOFFLE-LastAccess: %s\n", | |
| 429 msgId, | |
| 430 stat & DB_INTERESTING ? " INTERESTING" : "", | |
| 431 stat & DB_NOT_DOWNLOADED ? " NOT_DOWNLOADED" : "", | |
| 432 stat & DB_RETRIEVING_FAILED ? " RETRIEVING_FAILED" : "", | |
| 433 date ); | |
| 434 DynStr_app( s, t ); | |
| 435 appendLongHeader( s, "Subject:", db.subj ); | |
| 436 appendLongHeader( s, "From:", db.from ); | |
| 437 appendLongHeader( s, "Date:", db.date ); | |
| 438 appendLongHeader( s, "References:", db.ref ); | |
| 439 DynStr_app( s, "Bytes: " ); | |
| 440 snprintf( t, MAXCHAR, "%u", db.bytes ); | |
| 441 DynStr_appLn( s, t ); | |
| 442 DynStr_app( s, "Lines: " ); | |
| 443 snprintf( t, MAXCHAR, "%u", db.lines ); | |
| 444 DynStr_appLn( s, t ); | |
| 445 appendLongHeader( s, db.xrefHost, db.xref ); | |
| 446 p = strstr( DynStr_str( db.txt ), "\n\n" ); | |
| 447 if ( ! p ) | |
| 448 DynStr_appDynStr( s, db.txt ); | |
| 449 else | |
| 450 DynStr_appN( s, DynStr_str( db.txt ), p - DynStr_str( db.txt ) + 1 ); | |
| 451 return DynStr_str( s ); | |
| 452 } | |
| 453 | |
| 454 const char * | |
| 455 Db_body( const char *msgId ) | |
| 456 { | |
| 457 const char *p; | |
| 458 | |
| 459 if ( ! loadArt( msgId ) ) | |
| 460 return ""; | |
| 461 p = strstr( DynStr_str( db.txt ), "\n\n" ); | |
| 462 if ( ! p ) | |
| 463 return ""; | |
| 464 return ( p + 2 ); | |
| 465 } | |
| 466 | |
| 467 int | |
| 468 Db_stat( const char *msgId ) | |
| 469 { | |
| 470 if ( ! loadArt( msgId ) ) | |
| 471 return 0; | |
| 472 return db.stat; | |
| 473 } | |
| 474 | |
| 475 time_t | |
| 476 Db_lastAccess( const char *msgId ) | |
| 477 { | |
| 478 if ( ! loadArt( msgId ) ) | |
| 479 return -1; | |
| 480 return db.lastAccess; | |
| 481 } | |
| 482 | |
| 483 const char * | |
| 484 Db_ref( const char *msgId ) | |
| 485 { | |
| 486 if ( ! loadArt( msgId ) ) | |
| 487 return ""; | |
| 488 return db.ref; | |
| 489 } | |
| 490 | |
| 491 const char * | |
| 492 Db_xref( const char *msgId ) | |
| 493 { | |
| 494 if ( ! loadArt( msgId ) ) | |
| 495 return ""; | |
| 496 return db.xref; | |
| 497 } | |
| 498 | |
| 499 const char * | |
| 500 Db_from( const char *msgId ) | |
| 501 { | |
| 502 if ( ! loadArt( msgId ) ) | |
| 503 return ""; | |
| 504 return db.from; | |
| 505 } | |
| 506 | |
| 507 const char * | |
| 508 Db_date( const char *msgId ) | |
| 509 { | |
| 510 if ( ! loadArt( msgId ) ) | |
| 511 return ""; | |
| 512 return db.date; | |
| 513 } | |
| 514 | |
| 515 Bool | |
| 516 Db_contains( const char *msgId ) | |
| 517 { | |
| 518 datum key; | |
| 519 | |
| 520 ASSERT( db.dbf ); | |
| 521 if ( strcmp( msgId, db.msgId ) == 0 ) | |
| 522 return TRUE; | |
| 523 key.dptr = (void*)msgId; | |
| 524 key.dsize = strlen( msgId ) + 1; | |
| 525 return gdbm_exists( db.dbf, key ); | |
| 526 } | |
| 527 | |
| 528 void | |
| 529 Db_delete( const char *msgId ) | |
| 530 { | |
| 531 datum key; | |
| 532 | |
| 533 ASSERT( db.dbf ); | |
| 534 if ( strcmp( msgId, db.msgId ) == 0 ) | |
| 535 db.msgId[ 0 ] = '\0'; | |
| 536 key.dptr = (void*)msgId; | |
| 537 key.dsize = strlen( msgId ) + 1; | |
| 538 gdbm_delete( db.dbf, key ); | |
| 539 } | |
| 540 | |
| 541 static datum cursor = { NULL, 0 }; | |
| 542 | |
| 543 Bool | |
| 544 Db_first( const char** msgId ) | |
| 545 { | |
| 546 ASSERT( db.dbf ); | |
| 547 if ( cursor.dptr != NULL ) | |
| 548 { | |
| 549 free( cursor.dptr ); | |
| 550 cursor.dptr = NULL; | |
| 551 } | |
| 552 cursor = gdbm_firstkey( db.dbf ); | |
| 553 *msgId = cursor.dptr; | |
| 554 return ( cursor.dptr != NULL ); | |
| 555 } | |
| 556 | |
| 557 Bool | |
| 558 Db_next( const char** msgId ) | |
| 559 { | |
| 560 void *oldDptr = cursor.dptr; | |
| 561 | |
| 562 ASSERT( db.dbf ); | |
| 563 if ( cursor.dptr == NULL ) | |
| 564 return FALSE; | |
| 565 cursor = gdbm_nextkey( db.dbf, cursor ); | |
| 566 free( oldDptr ); | |
| 567 *msgId = cursor.dptr; | |
| 568 return ( cursor.dptr != NULL ); | |
| 569 } | |
| 570 | |
| 571 static int | |
| 572 calcExpireDays( const char *msgId ) | |
| 573 { | |
| 574 const char *xref; | |
| 575 ItemList *refs; | |
| 576 const char *ref; | |
| 577 int res; | |
| 578 | |
| 579 xref = Db_xref( msgId ); | |
| 580 if ( xref[ 0 ] == '\0' ) | |
| 581 return -1; | |
| 582 | |
| 583 res = -1; | |
| 584 refs = new_Itl( xref, " :" ); | |
| 585 for ( ref = Itl_first( refs ); ref != NULL; ref = Itl_next( refs ) ) | |
| 586 { | |
| 587 Str pattern; | |
| 588 int days; | |
| 589 | |
| 590 Cfg_beginExpireEnum(); | |
| 591 while ( ( days = Cfg_nextExpire( pattern ) ) != -1 ) | |
| 592 if ( Wld_match( ref, pattern ) | |
| 593 && ( ( days > res && res != 0 ) || | |
| 594 days == 0 ) ) | |
| 595 { | |
| 596 res = days; | |
| 597 Log_dbg ( "Custom expiry %d for %s in group %s", | |
| 598 days, msgId, ref ); | |
| 599 break; | |
| 600 } | |
| 601 | |
| 602 Itl_next( refs ); /* Throw away group number */ | |
| 603 } | |
| 604 | |
| 605 if ( res == -1 ) | |
| 606 res = Cfg_expire(); | |
| 607 return res; | |
| 608 } | |
| 609 | |
| 610 Bool | |
| 611 Db_expire( void ) | |
| 612 { | |
| 613 int cntDel, cntLeft, flags, expDays; | |
| 614 time_t nowTime, lastAccess; | |
| 615 const char *msgId; | |
| 616 Str name, tmpName; | |
| 617 GDBM_FILE tmpDbf; | |
| 618 datum key, val; | |
| 619 | |
| 620 if ( ! Db_open() ) | |
| 621 return FALSE; | |
| 622 snprintf( name, MAXCHAR, "%s/data/articles.gdbm", Cfg_spoolDir() ); | |
| 623 snprintf( tmpName, MAXCHAR, "%s/data/articles.gdbm.new", Cfg_spoolDir() ); | |
| 624 flags = GDBM_NEWDB | GDBM_FAST; | |
| 625 if ( ! ( tmpDbf = gdbm_open( tmpName, 512, flags, 0644, NULL ) ) ) | |
| 626 { | |
| 627 Log_err( "Error opening %s for read/write (%s)", errMsg() ); | |
| 628 Db_close(); | |
| 629 return FALSE; | |
| 630 } | |
| 631 Log_inf( "Expiring articles" ); | |
| 632 cntDel = 0; | |
| 633 cntLeft = 0; | |
| 634 nowTime = time( NULL ); | |
| 635 if ( Db_first( &msgId ) ) | |
| 636 do | |
| 637 { | |
| 638 expDays = calcExpireDays( msgId ); | |
| 639 lastAccess = Db_lastAccess( msgId ); | |
| 640 if ( expDays == -1 ) | |
| 641 Log_err( "Internal error: Failed expiry calculation on %s", | |
| 642 msgId ); | |
| 643 else if ( lastAccess == -1 ) | |
| 644 Log_err( "Internal error: Getting lastAccess of %s failed", | |
| 645 msgId ); | |
| 646 else if ( expDays > 0 | |
| 647 && difftime( nowTime, lastAccess ) > | |
| 648 ( (double) expDays * 24 * 3600 ) ) | |
| 649 { | |
| 650 #ifdef DEBUG | |
| 651 Str last, now; | |
| 652 | |
| 653 Utl_cpyStr( last, ctime( &lastAccess ) ); | |
| 654 last[ strlen( last ) - 1 ] = '\0'; | |
| 655 Utl_cpyStr( now, ctime( &nowTime ) ); | |
| 656 last[ strlen( now ) - 1 ] = '\0'; | |
| 657 Log_dbg( "Expiring %s: last access %s, time now %s", | |
| 658 msgId, last, now ); | |
| 659 #endif | |
| 660 ++cntDel; | |
| 661 } | |
| 662 else | |
| 663 { | |
| 664 ++cntLeft; | |
| 665 key.dptr = (void *)msgId; | |
| 666 key.dsize = strlen( msgId ) + 1; | |
| 667 | |
| 668 val = gdbm_fetch( db.dbf, key ); | |
| 669 if ( val.dptr != NULL ) | |
| 670 { | |
| 671 if ( gdbm_store( tmpDbf, key, val, GDBM_INSERT ) != 0 ) | |
| 672 Log_err( "Could not store %s in new database (%s)", | |
| 673 errMsg() ); | |
| 674 free( val.dptr ); | |
| 675 } | |
| 676 } | |
| 677 } | |
| 678 while ( Db_next( &msgId ) ); | |
| 679 Log_inf( "%lu articles deleted, %lu left", cntDel, cntLeft ); | |
| 680 gdbm_close( tmpDbf ); | |
| 681 Db_close(); | |
| 682 rename( tmpName, name ); | |
| 683 return TRUE; | |
| 684 } |
