/* -*-C-*-
*******************************************************************************
*
* File:         compactmail.m
* RCS:          /usr/local/sources/CVS/mailapp-utilities/compactmail.m,v 1.4 1997/04/19 17:37:19 tom Exp
* Description:  Compact Mail.app mailboxes (expunge deleted messages)
* Author:       Carl Edman
* Created:      Fri Mar 12 18:21:23 1993
* Modified:     Sat Nov 30 23:28:56 1996 Tom Hageman <tom@basil.icce.rug.nl>
* Language:     Objective C
* Package:      mailapp-utilities
* Status:       First release.
*
* (C) Copyright 1993, but otherwise this file is perfect freeware.
*
*******************************************************************************
*/

#import <libc.h>
#import <errno.h>
//#import <stdlib.h>
//#import <stdio.h>
//#import <string.h>
//#import <time.h>
#import <regex.h>
//#import <sys/file.h>
//#import <sys/param.h>
//#import <sys/types.h>
//#import <sys/stat.h>
#import <defaults/defaults.h>
#import "mailutil.h"
#import "mailtoc.h"
#import "optutil.h"

#define USAGE "\
Usage: %s [-nruv] [-d days] [-f from] [-s subject] [-V|-H] mbox...\n"

#define HELP "\
 -n           skip mailbox if locked\n\
 -r           expunge read messages too (default deleted messages only)\n\
 -u           expunge all messages (even unread)\n\
 -v           talkative mode\n\
 -d days      restrict to messages of at least `days' days old.\n\
 -f from      restrict to messages whose From: header matches (regexp)\n\
 -s subject   restrict to messages whose Subject: header matches (regexp)\n\
 -V           show version\n\
 -H           this help\n\
"

char line[LINELEN];

int main(int ac,char *av[])
{
   char *buf=0;
   int c,pos;
   int nowaitflg=0,verboseflg=0,deletereadflg=0,deleteunreadflg=0;
   int days=-1;
   struct regex *subjectregexp=0,*fromregexp=0;
   time_t mboxtime;
   int mboxdis,tocdis,msgdis,changed;
   int mboxfd=-1;
   FILE *tocf=NULL;
   struct table_of_contents_header *toch=0;
   struct message_index *mi=0;
   int status=EXIT_SUCCESS;

   set_progname(av[0]);

   while((c=getopt(ac,av,"d:s:f:nruvVH"))!=EOF) switch(c)
   {
    case 'd':
      if (days!=-1 || (days=atoi(optarg))<=0)
         status=EXIT_USAGE;
      break;
    case 's':
      if (subjectregexp || !(subjectregexp=re_compile(optarg,0)))
         status=EXIT_USAGE;
      break;
    case 'f':
      if (fromregexp || !(fromregexp=re_compile(optarg,0)))
         status=EXIT_USAGE;
      break;
    case 'n':
      nowaitflg++;
      break;
    case 'r':
      deletereadflg++;
      break;
    case 'u':
      deleteunreadflg++;
      break;
    case 'v':
      verboseflg++;
      break;
    case 'V':
      status=EXIT_VERSION;
      break;
    case 'H':
      status=EXIT_HELP;
      break;
    case '?':
    default:
      status=EXIT_USAGE;
   }
   if (status==EXIT_SUCCESS && optind>=ac) status=EXIT_USAGE;
   handle_usage_help_version(status, USAGE, HELP);

   for(;optind<ac;optind++)
   {
      if (verboseflg) fprintf(stderr,"Entering %s...\n",av[optind]);
      if (cd_mbox(av[optind],1)) goto next;
      if (lock_mbox(nowaitflg)) goto next;

      mboxtime=mtime("mbox");

      if (((mboxfd=open("mbox",O_RDWR))==-1) ||
          ((tocf=fopen("table_of_contents","r+b"))==NULL))
      {
         fprintf(stderr,"%s: opening %s: %s\n",progname(),av[optind],strerror(errno));
         status=EXIT_FAILURE;
         goto unlock;
      }

      toch=get_table_of_contents_header(tocf,0);
      if (!toch)
      {
         fprintf(stderr,"%s: %s: invalid old table_of_contents\n",progname(),av[optind]);
         status=EXIT_FAILURE;
         goto unlock;
      }

      if (toch->mbox_time!=mboxtime)
      {
         fprintf(stderr,"%s: %s: table of contents out of sync\n",progname(),av[optind]);
         status=EXIT_FAILURE;
         goto unlock;
      }

      for(changed=FALSE,msgdis=mboxdis=tocdis=c=0;c<toch->num_msgs;c++)
      {
         if ((mi=get_message_index(tocf))==0)
	 {
            fprintf(stderr,"%s: %s: reading old mbox entries: %s\n",progname(),av[optind],(errno?strerror(errno):"invalid"));
            status=EXIT_FAILURE;
            goto unlock;
	 }
         if ((mi->status=='d' || mi->status=='D' || deleteunreadflg ||
              (mi->status!='*' && deletereadflg)) &&
             (days==-1 || message_age(mi)>=days) &&
             (!subjectregexp || re_match(message_subject(mi),subjectregexp)) &&
             (!fromregexp || re_match(message_from(mi),fromregexp)))
	 {
            if (verboseflg>1)
	    {
               fprintf(stderr,"Expunging \"%s\" from %s (%d days old).\n",message_subject(mi),message_from(mi),message_age(mi));
	    }
            if (mi->msgtype=='r')
	    {
               char *dir=mi->data;
               dir+=strlen(dir)+1;
               dir+=strlen(dir)+1;
               if (*dir)
	       {
		  int pid,childpid=fork();
		  union wait status;

		  switch (childpid)
		  {
		  case -1:
		     break;
		  case 0:
		     execl("/bin/rm", "rm", "-rf", dir, NULL);
		     _exit(255);
		  default:
		     while ((pid=wait(&status))>=0 && pid!=childpid) ;
		  }
	       }
	    }
            changed=TRUE;
            msgdis++;
            tocdis +=mi->record_length;
            mboxdis+=mi->mes_length;
	 }
         else
	 {
            if (verboseflg>2)
	    {
               fprintf(stderr,"Keeping \"%s\" from %s (%d days old).\n",message_subject(mi),message_from(mi),message_age(mi));
	    }
            if (mboxdis!=0)
	    {
               lseek(mboxfd,mi->mes_offset,L_SET);
               buf=malloc(mi->mes_length);
               read(mboxfd,buf,mi->mes_length);
               mi->mes_offset -= mboxdis;
               lseek(mboxfd,mi->mes_offset,L_SET);
               write(mboxfd,buf,mi->mes_length);
               free(buf); buf=0;
	    }

            if (tocdis!=0 || mboxdis!=0)
	    {
               pos=ftell(tocf);
               fseek(tocf,-mi->record_length-tocdis,SEEK_CUR);
               put_message_index(tocf,mi);
               fseek(tocf,pos,SEEK_SET);
	    }
	 }
         free(mi); mi=0;
      }
      
      if (tocdis!=0)
      {
	 fflush(tocf);
         pos=fseek(tocf,0,SEEK_END);
         ftruncate(fileno(tocf),pos-tocdis);
      }

      if (mboxdis!=0)
      {
         pos=lseek(mboxfd,0,L_XTND);
         ftruncate(mboxfd,pos-mboxdis);
      }
      close(mboxfd), mboxfd=-1;
      
      toch->num_msgs-=msgdis;
      mboxtime=mtime("mbox");
      if ((toch->mbox_time!=mboxtime) || changed)
      {
         toch->mbox_time=mboxtime;
         fseek(tocf,0,SEEK_SET);
         if (put_table_of_contents_header(tocf,toch))
	 {
            fprintf(stderr,"%s: %s: writing updated table_of_contents: %s\n",progname(),av[optind],strerror(errno));
            status=EXIT_FAILURE;
            goto unlock;
	 }
      }
    unlock:
      if(mboxfd>=0) close(mboxfd), mboxfd=-1;
      if(tocf!=NULL) fclose(tocf), tocf=NULL;
      unlock_mbox();
    next:;
      if (toch) { free(toch); toch=0; }
      if (mi) { free(mi); mi=0; }
      if (buf) { free(buf); buf=0; }
      uncd_mbox();
   }
   exit(status);
}
