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