view src/expire.c @ 255:52f467c7213b noffle

[svn] * docs/noffle.1,src/Makefile.am,src/Makefile.in,src/content.c, src/content.h,src/database.c,src/database.h,src/expire.c, src/expire.h,src/noffle.c: Split out expire code from database.c, change to remove articles in place (rather than rebuild article database) and add separate command to rebuild article database from articles listed in overviews. This may help if the article database gets corrupted.
author bears
date Wed, 26 Jun 2002 14:15:44 +0100
parents
children
line wrap: on
line source

/*
  expire.c

  $Id: expire.c 387 2002-06-26 13:15:44Z bears $

  Handle expiring articles from the article base.
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "configfile.h"
#include "content.h"
#include "database.h"
#include "expire.h"
#include "fetchlist.h"
#include "group.h"
#include "itemlist.h"
#include "log.h"
#include "protocol.h"
#include "pseudo.h"
#include "util.h"
#include "portable.h"

/*
 * Find the maximum expire time in days for this article.
 * Different groups may have different limits, so we need to
 * check the limit for each group.
 */
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 ) )
    {
	int days;

	days = Cfg_expire( ref );
	if ( days == 0
	     || ( days > res && res != 0 ) )
	    res = days;
	
	Itl_next( refs );	/* Throw away group number */
    }
    del_Itl( refs );

    return res;
}

/* Does this article need to be expired? */
static Bool
articleExpired( const char *msgId, time_t now )
{
    int expDays;
    time_t lastAccess;
    Str expires;
    time_t texpires;

    expDays = calcExpireDays( msgId );
    if ( expDays == -1 )
    {
	Log_err( "Internal error: Failed expiry calculation on %s",
		 msgId );
	return TRUE;
    }
    
    lastAccess = Db_lastAccess( msgId );
    if ( lastAccess == -1 )
    {
	Log_err( "Internal error: Getting lastAccess of %s failed",
		 msgId );
	return TRUE;
    }
    
    if ( Prt_searchHeader( Db_header( msgId ), "Expires", expires ) )
	texpires = Utl_parseNewsDate( expires );
    else
	texpires = (time_t) -1;
	    
    if ( expDays > 0 &&
	 difftime( now, lastAccess ) > ( (double) expDays * 24 * 3600 ) )
    {
#ifdef DEBUG
	Str lastStr, nowStr;

	Utl_cpyStr( lastStr, ctime( &lastAccess ) );
	lastStr[ strlen( lastStr ) - 1 ] = '\0';
	Utl_cpyStr( nowStr, ctime( &now ) );
	nowStr[ strlen( nowStr ) - 1 ] = '\0';
	Log_dbg( LOG_DBG_EXPIRE,
		 "Expiring %s: last access %s, time now %s",
		 msgId, lastStr, nowStr );
#endif
    }
    else if ( ( texpires != (time_t) -1 ) && now > texpires )
    {
	Log_dbg( LOG_DBG_EXPIRE,
		 "Expiring %s: Expires header activated", msgId );
    }
    else
	return FALSE;

    return TRUE;
}

/* Work though all overviews looking for articles to expire. */
void
Exp_expire( void )
{
    const Over *ov;
    int i;
    int cntDel, cntLeft;
    Str grp;
    Bool autoUnsubscribe;
    int autoUnsubscribeDays;
    time_t now, maxAge = 0;
    const char *msgId;

    autoUnsubscribe = Cfg_autoUnsubscribe();
    autoUnsubscribeDays = Cfg_autoUnsubscribeDays();
    maxAge = Cfg_autoUnsubscribeDays() * 24 * 3600;
    if ( ! Cont_firstGrp( grp ) )
        return;
    Log_inf( "Expiring articles" );
    Fetchlist_read();
    now = time( NULL );
    do
    {
	if ( ! Grp_exists( grp ) )
            Log_err( "Overview file for unknown group %s exists", grp );
        else
        {
            cntDel = cntLeft = 0;
            Cont_read( grp );
            for ( i = Cont_first(); i <= Cont_last(); ++i )
	    {
		if ( ! Cont_validNumb( i ) )
		    continue;
		
                if ( ( ov = Cont_get( i ) ) )
                {
                    msgId = Ov_msgId( ov );
		    /* Crossposted articles may have already been deleted. */
		    if ( ! Db_contains( msgId ) )
		    {
			Cont_delete( i );
			++cntDel;
		    } else if ( articleExpired( msgId, now ) )
                    {
                        Cont_delete( i );
			Db_delete( msgId );
                        ++cntDel;
                    }
                    else
                        ++cntLeft;
                }
	    }

	    /*
	     * Auto unsubscribe where applicable if last article arrival
	     * time is maxAge newer than the last access time. This ensures
	     * the low traffic groups don't get expired simply because
	     * there's been nothing to read.
	     */
            if ( ! Grp_local( grp )
                 && Fetchlist_contains( grp, NULL )
                 && autoUnsubscribe
                 && difftime( Grp_lastPostTime(grp),
			      Grp_lastAccess( grp ) ) > maxAge )
            {
		Log_ntc( "Auto-unsubscribing from %s after %d "
			 "days without access",
			 grp, autoUnsubscribeDays );
		Pseudo_autoUnsubscribed( grp, autoUnsubscribeDays );
		Fetchlist_remove( grp );
		Grp_setRmtNext( grp, GRP_RMT_NEXT_NOT_SUBSCRIBED );
            }
            if ( Cont_write() )
                Grp_setFirstLast( grp, Cont_first(), Cont_last() );
            Log_inf( "%ld overviews deleted from group %s, %ld left (%ld-%ld)",
                     cntDel, grp, cntLeft, Grp_first( grp ), Grp_last( grp ) );
        }
    }
    while ( Cont_nextGrp( grp ) );
    Fetchlist_write();
    Db_compact();
}