/*--------------------------------------------------------------------*/
/*    e x p i r e . c                                                 */
/*                                                                    */
/*    Expire old news articles for UUPC/extended                      */
/*                                                                    */
/*    Copyright (c) 1992-1993 by Kendra Electronic Wonderworks, all   */
/*    rights reserved except those explicitly granted by the UUPC/    */
/*    extended license.                                               */
/*--------------------------------------------------------------------*/

/*
 *    $Id: EXPIRE.C 1.4 1993/04/05 04:32:19 ahd Exp $
 *
 *    $Log: EXPIRE.C $
 * Revision 1.4  1993/04/05  04:32:19  ahd
 * Add timestamp, size to information returned by directory searches
 *
 * Revision 1.3  1992/11/25  12:59:17  ahd
 * Modifiy summery messages
 *
 */

/*--------------------------------------------------------------------*/
/*                        System include files                        */
/*--------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <limits.h>

/*--------------------------------------------------------------------*/
/*                    UUPC/extended include files                     */
/*--------------------------------------------------------------------*/

#include "lib.h"
#include "active.h"
#include "dater.h"
#include "getopt.h"
#include "hlib.h"
#include "import.h"
#include "importng.h"
#include "logger.h"
#include "uundir.h"
#include "pushpop.h"
#include "stater.h"
#include "timestmp.h"

currentfile();

/*--------------------------------------------------------------------*/
/*                          Global Variables                          */
/*--------------------------------------------------------------------*/

#define ONE_DAY (60L*60L*24L)

/*--------------------------------------------------------------------*/
/*                        Internal prototypes                         */
/*--------------------------------------------------------------------*/

static boolean numeric( char *start);

static void ExpireAll( const time_t expire_date,
                       const time_t archive_date );

static void  ExpireGroup( const char *group,
                      const time_t expire_date,
                      const time_t archive_date );

static void ExpireOneGroup( struct grp *cur_grp,
                      const time_t expire_date,
                      const time_t archive_date );

static void ExpireDirectory( struct grp *cur_grp,
                      const time_t expire_date,
                      const char *directory,
                      const char *archive );

static boolean numeric( char *start);

static void usage( void );

long total_articles_purged   = 0;
long total_articles_archived = 0;
long total_articles_kept     = 0;
long total_bytes_purged   = 0;
long total_bytes_archived = 0;
long total_bytes_kept     = 0;

/*--------------------------------------------------------------------*/
/*    m a i n                                                         */
/*                                                                    */
/*    Main program                                                    */
/*--------------------------------------------------------------------*/

void main( int argc, char **argv)
{
   int c;
   extern char *optarg;
   extern int   optind;
   char *group = NULL;

   time_t expire_period  = 7; /* Seven days visible to users         */
   time_t archive_period = 0; /* Seven days after expiring in arch   */

   time_t expire_date;
   time_t archive_date;

/*--------------------------------------------------------------------*/
/*     Report our version number and date/time compiled               */
/*--------------------------------------------------------------------*/

   debuglevel = 1;
   banner( argv );

#if defined(__CORE__)
   copywrong = strdup(copyright);
   checkref(copywrong);
#endif

/*--------------------------------------------------------------------*/
/*        Process our arguments                                       */
/*--------------------------------------------------------------------*/

   while ((c = getopt(argc, argv, "e:a:g:x:n:")) !=  EOF)
      switch(c) {

      case 'a':
         archive_period = atoi( optarg );
         break;

      case 'e':
         expire_period = atoi( optarg );
         break;

      case 'n':
         group = optarg;
         break;

      case 'x':
         debuglevel = atoi( optarg );
         break;

      case '?':
         usage();
         exit(1);
         break;

      default:
         printmsg(0, "expire - invalid option -%c", c);
         usage();
         exit(2);
         break;
   }

   if (optind != argc) {
      fputs("Extra parameter(s) at end.\n", stderr);
      usage();
      exit(2);
   }

/*--------------------------------------------------------------------*/
/*                             Initialize                             */
/*--------------------------------------------------------------------*/

   tzset();                      /* Set up time zone information  */

   if (!configure( B_NEWS ))
      exit(1);   /* system configuration failed */

/*--------------------------------------------------------------------*/
/*                  Switch to the spooling directory                  */
/*--------------------------------------------------------------------*/

   PushDir( E_newsdir );
   atexit( PopDir );

/*--------------------------------------------------------------------*/
/*                     Initialize logging file                        */
/*--------------------------------------------------------------------*/

   openlog( NULL );

/*--------------------------------------------------------------------*/
/*                       Load the active file                         */
/*--------------------------------------------------------------------*/

   get_active();              /* Get sequence numbers for groups from
                                 active file                      */

/*--------------------------------------------------------------------*/
/*                  Compute times for expiring files                  */
/*--------------------------------------------------------------------*/

   time( &expire_date );
   expire_date  -= (expire_period * ONE_DAY);
   archive_date = expire_date - (archive_period * ONE_DAY);

   printmsg(1,"%s: %sing news older than %s (%ld days)",
            argv[0],
            archive_period ?  "Archiv" : "Purg",
            dater( expire_date , NULL), (long) expire_period );

   if ( archive_period != 0 )
      printmsg(1,"%s: Purging news older than %s",
            argv[0],
                  dater( archive_date , NULL));

/*--------------------------------------------------------------------*/
/*    Process one group if requested, otherwise process the entire    */
/*    active file                                                     */
/*--------------------------------------------------------------------*/

      if ( group != NULL )
         ExpireGroup( group, expire_date, archive_date );
      else
         ExpireAll(expire_date, archive_date );

/*--------------------------------------------------------------------*/
/*                         Clean up and exit                          */
/*--------------------------------------------------------------------*/

   put_active();

   if ( total_articles_purged || total_articles_archived)
      printmsg(1,"%s: Purged %ld total articles (%ld bytes), "
                    "archived %ld total articles (%ld bytes). " ,
                  argv[0],
                  total_articles_purged,   total_bytes_purged,
                  total_articles_archived, total_bytes_archived );

   printmsg(1,"%s: Left alone %ld articles (%ld bytes).  "
                 "Total of %ld articles now use %ld bytes." ,
               argv[0],
               total_articles_kept,     total_bytes_kept,
               total_articles_kept + total_articles_archived,
               total_bytes_kept + total_bytes_archived );

   exit(0);

} /* main */

/*--------------------------------------------------------------------*/
/*    E x p i r e A l l                                               */
/*                                                                    */
/*    Expire all defined news groups                                  */
/*--------------------------------------------------------------------*/

static void ExpireAll( const time_t expire_date,
                const time_t archive_date )
{
   struct grp *cur_grp = group_list;

   while ( cur_grp != NULL )
   {
      ExpireOneGroup( cur_grp, expire_date, archive_date );
                                    /* Clean up this group           */

      cur_grp = cur_grp->grp_next;  /* Then clean up the next group  */
   }
} /* Expire_All */

/*--------------------------------------------------------------------*/
/*    E x p i r e G r o u p                                           */
/*                                                                    */
/*    Clean up one group by name                                      */
/*--------------------------------------------------------------------*/

static void  ExpireGroup( const char *group,
                      const time_t expire_date,
                      const time_t archive_date )
{
   struct grp *cur_grp = group_list;
   struct grp *target = NULL;

/*--------------------------------------------------------------------*/
/*         Search the list of groups for the requested group          */
/*--------------------------------------------------------------------*/

   while ( (cur_grp != NULL) && (target == NULL))
   {
      if ( equal( cur_grp->grp_name, group ))
         target = cur_grp;

      cur_grp = cur_grp->grp_next;  /* Then clean up the next group  */
   }

/*--------------------------------------------------------------------*/
/*   If we found the group, process it, otherwise report the error    */
/*--------------------------------------------------------------------*/

   if ( target == NULL )
      printmsg(0,"Unable to locate active group %s", group );
   else
      ExpireOneGroup( target, expire_date, archive_date );
                                    /* Clean up this group           */

} /* ExpireGroup */

/*--------------------------------------------------------------------*/
/*    E x p i r e O n e G r o u p                                     */
/*                                                                    */
/*    Clean up one group by name                                      */
/*--------------------------------------------------------------------*/

static void ExpireOneGroup( struct grp *cur_grp,
                      const time_t expire_date,
                      const time_t archive_date )
{
   char groupdir[FILENAME_MAX];
   char archdir[FILENAME_MAX];

   printmsg(3,"Processing news group %s", cur_grp->grp_name );

/*--------------------------------------------------------------------*/
/*                     Set up the directory names                     */
/*--------------------------------------------------------------------*/

   ImportNewsGroup( groupdir, cur_grp->grp_name, 0 );
   mkfilename( archdir, E_archivedir, &groupdir[ strlen( E_newsdir) + 1] );

/*--------------------------------------------------------------------*/
/*            Process the primary and archive directories             */
/*--------------------------------------------------------------------*/

   ExpireDirectory( cur_grp, archive_date, archdir, NULL);
                              /* Purge archive first to keep
                                 directory smaller                   */

   ExpireDirectory( cur_grp, expire_date, groupdir,
                    (archive_date < expire_date) ? archdir : NULL);
                              /* Do not archive for 0 days!          */

} /* ExpireOneGroup */

/*--------------------------------------------------------------------*/
/*    E x p i r e D i r e c t o r y                                   */
/*                                                                    */
/*    Clean up one group by name                                      */
/*--------------------------------------------------------------------*/

static void ExpireDirectory( struct grp *cur_grp,
                      const time_t expire,
                      const char *directory,
                      const char *archive )
{
   char fname[FILENAME_MAX];

   int articles_archived = 0;/* Count of files moved to archive        */
   int articles_purged   = 0;/* Count of files actually deleted        */
   int articles_kept     = 0;/* Count of files actually deleted        */
   long bytes_purged = 0;    /* Bytes freed on disk from deletions     */
   long bytes_kept   = 0;    /* Bytes left on disk total               */
   long bytes_archived = 0;  /* Bytes left on disk in archive          */

   boolean not_built = TRUE;  /* Did not insure archive directory
                                 exists                           */

   long low = LONG_MAX;  /* Oldest article number left             */

   DIR *dirp;
   struct direct *dp;

/*--------------------------------------------------------------------*/
/*                Open up the directory for processing                */
/*--------------------------------------------------------------------*/

   if ((dirp = opendirx(directory,"*.*")) == nil(DIR))
   {
      printmsg(3, "ExpireDirectory: couldn't opendir() %s", directory);
      cur_grp->grp_low = cur_grp->grp_high;
      return;
   } /* if */

/*--------------------------------------------------------------------*/
/*                 Switch to directory for processing                 */
/*--------------------------------------------------------------------*/

   CHDIR( directory );

/*--------------------------------------------------------------------*/
/*              Look for the next file in the directory               */
/*--------------------------------------------------------------------*/

   while((dp = readdir(dirp)) != nil(struct direct))
   {

/*--------------------------------------------------------------------*/
/*                      Archive/expire this file?                     */
/*--------------------------------------------------------------------*/

      if ( numeric( dp->d_name ))/* Article format name?             */
      {                          /* Yes --> Examine it closer        */

         printmsg(6,"Processing file %s from %s",
                  dp->d_name, dater( dp->d_modified, NULL));

         if ( dp->d_modified < expire )   /* Long in the tooth?      */
         {                       /* Yes --> Move it on out           */
            int not_processed = TRUE;
                              /* Purge unless we archive it          */

            if ( archive != NULL )
            {
               printmsg( 4,"Archiving file %s from %s to %s",
                        dp->d_name, directory , archive);

               if ( not_built )  /* First pass into directory?    */
               {                 /* Yes --> Make sure it exists   */
                  MKDIR( archive );
                  not_built = FALSE;   /* Only build it once      */
               }

               mkfilename( fname, archive, dp->d_name);
               not_processed = rename( dp->d_name, fname );

               if ( not_processed )
               {
                  printerr( fname );
                  printmsg(0,"Rename %s to %s failed, purging file.",
                           dp->d_name, fname);
               }
               else {
                  articles_archived++;
                  bytes_archived += dp->d_size;
               }
            } /* if ( archive != NULL ) */

            if ( not_processed )
            {
               printmsg( 4,"Purging file %s from %s", dp->d_name, directory );
               unlink( dp->d_name );
               articles_purged++;
               bytes_purged += dp->d_size;
            } /* if ( not_processed ) */

         } /* if ( dp->d_modified < expire ) */

/*--------------------------------------------------------------------*/
/*    If the article is valid and still in the main news              */
/*    directory, determine if it is the lowest article left           */
/*--------------------------------------------------------------------*/

         else {
            long article = 0;
            char *digit = dp->d_name;

            while( *digit )
               article = article * 10 + (*digit++ - '0');

            low = min( article, low );

            bytes_kept += dp->d_size;
            articles_kept ++;

         } /* else if ( archive != NULL ) */
      } /* if ( numeric( dp->d_name ) */

   } /* while */

/*--------------------------------------------------------------------*/
/*            Update lowest article available to the users            */
/*--------------------------------------------------------------------*/

   if ( low == LONG_MAX )
       cur_grp->grp_low = cur_grp->grp_high;
   else
       cur_grp->grp_low = low;

/*--------------------------------------------------------------------*/
/*           Close up the directory and report what we did            */
/*--------------------------------------------------------------------*/

   closedir(dirp);

   if ( articles_archived )
      printmsg(2,"%s: Purged %d articles (%ld bytes),"
                    " archived %d articles (%ld bytes),"
                    " left alone %d articles (%ld bytes).",
                  cur_grp->grp_name,
                  articles_purged, bytes_purged,
                  articles_archived, bytes_archived,
                  articles_kept, bytes_kept );
   else if ( articles_purged )
      printmsg(2,"%s: Purged %d articles (%ld bytes),"
                    " left alone %d articles (%ld bytes).",
                  cur_grp->grp_name,
                  articles_purged, bytes_purged,
                  articles_kept, bytes_kept );
   else if ( articles_kept )
      printmsg(2,"%s: Left alone %d articles (%ld bytes).",
                  cur_grp->grp_name,
                  articles_kept, bytes_kept );

   total_articles_archived += articles_archived;
   total_articles_kept     += articles_kept;
   total_articles_purged   += articles_purged;
   total_bytes_archived    += bytes_archived;
   total_bytes_kept        += bytes_kept;
   total_bytes_purged      += bytes_purged;

} /* ExpireDirectory */

/*--------------------------------------------------------------------*/
/*    n u m e r i c                                                   */
/*                                                                    */
/*    Examines string, returns true if numeric with period            */
/*--------------------------------------------------------------------*/

 static boolean numeric( char *start)
 {
   char *number = start;

   while (*number != '\0')
   {
      if (!isdigit(*number) && (*number != '.'))
         return FALSE;

      number++;
   }

   return TRUE;
 } /* numeric */

/*--------------------------------------------------------------------*/
/*    u s a g e                                                       */
/*                                                                    */
/*    Print usage of program                                          */
/*--------------------------------------------------------------------*/

static void usage( void )
{
   printf( "Usage:   expire [-edays] [-adays] [-ngroup]\n");
   exit(1);
} /* usage */
