/*
 * WMMail - Window Maker Mail
 *
 * Copyright (c) 1996, 1997, 1998  Per Liden
 * Copyright (c) 1997, 1998  Bryan Chan
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * mbox.c: functions to handle UNIX-style mailboxes
 *
 * $Id: mbox.c,v 1.2 2000/07/03 08:49:24 bryan.chan Exp $
 *
 */

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <utime.h>
#include <unistd.h>
#include <proplist.h>
#include "wmmail.h"
#include "wmutil.h"

#ifdef MBOX_SUPPORT

/* 
 * Supported key(s) in the Options dictionary:
 *
 *   MailboxHasInternalData
 *   CheckTimeStampOnly
 *   Path
 *
 * We can classify a given mailbox as being in one of four states.
 * The states and their corresponding effects on wmmail_status are
 * as follows:
 *
 *   - no mailbox or empty mailbox              NO_MAIL
 *   - no new mails                             OLD_MAIL
 *   - same number of new mails or fewer        no change
 *   - more new mails than last time            NEW_MAIL
 *
 * Or, if CheckTimeStampOnly is Yes:
 *
 *   - no mailbox or empty mailbox              NO_MAIL
 *   - smaller than last time but non-zero      OLD_MAIL
 *   - read after most recent write             OLD_MAIL
 *   - no read after most recent write and
 *     - same size as last time                 no change
 *     - bigger than last time                  NEW_MAIL
 *
 * We assume that overriding automatically changes wmmail_status
 * to OLD_MAIL.
 *
 */

/* internal functions */
static int check_time_stamp(Mailbox *, int *, int *, int *);
static int get_new_count(Mailbox *, int *, int *, int *);
static int count_mail(Mailbox *, char *);


#define GET_ENTRY(x,y,z) { proplist_t sub_key = PLMakeString(z); \
                           y = PLGetDictionaryEntry(x, sub_key); \
                           PLRelease(sub_key); }

int MBOX_check(Mailbox *mailbox, int *beep, int *redraw, int *run)
{
  proplist_t  check_time_stamp_only;
  int         okay, forced_no = False;
  
  GET_ENTRY(mailbox->options, check_time_stamp_only, "CheckTimeStampOnly");

  if ( check_time_stamp_only != NULL && PLIsString(check_time_stamp_only) &&
       !strcasecmp(PLGetString(check_time_stamp_only), "Yes") )
  {
    if (num_of_msg_mode != SHOW_NONE)
    {
      croak("cannot count messages when CheckTimeStampOnly is \"Yes\"; "
            "\"No\" assumed");
 
      okay = get_new_count(mailbox, beep, redraw, run);
      forced_no = True;
    }
    else
      okay = check_time_stamp(mailbox, beep, redraw, run);
  }
  else 
    okay = get_new_count(mailbox, beep, redraw, run);

  if ( forced_no ||
       ( check_time_stamp_only != NULL &&
         ( !PLIsString(check_time_stamp_only) ||
           ( strcasecmp(PLGetString(check_time_stamp_only), "Yes") &&
             strcasecmp(PLGetString(check_time_stamp_only), "No") )   ) ) )
  {
    proplist_t label;

    if (!forced_no)
      croak("boolean expected for key \"CheckTimeStampOnly\"; "
            "\"No\" assumed");

    label = PLMakeString("CheckTimeStampOnly");
    check_time_stamp_only = PLMakeString("No");
    PLInsertDictionaryEntry(mailbox->options, label, check_time_stamp_only);
    PLRelease(check_time_stamp_only);
    PLRelease(label);
  }

  return okay;
}


static int check_time_stamp(Mailbox *mailbox, int *beep, int *redraw, int *run)
{
  proplist_t   path;
  struct stat  t;
  char        *mailbox_path;
  int          prev_status;

  prev_status = mailbox->status;

  GET_ENTRY(mailbox->options, path, "Path");
  if (path == NULL)
  {
    croak("mailbox \"%s\" missing option \"Path\"; ignored", mailbox->name);
    return False;
  }
  else if (!PLIsString(path))
  {
    croak("mailbox \"%s\" has invalid path; ignored", mailbox->name);
    return False;
  }
  else
    mailbox_path = expand_path(PLGetString(path));

  if (!stat(mailbox_path, &t))
  {
#ifdef DEBUG
    croak("%s: %s", mailbox->name, mailbox_path);
    croak("size: %d mtime: %d atime: %d", t.st_size, t.st_mtime, t.st_atime);
#endif
    if (t.st_size == 0)
    {
      mailbox->status = NO_MAIL;
    }
    else if (t.st_size < mailbox->size)
    {
      /* mailbox smaller in size; some mails have been deleted */
      mailbox->status = OLD_MAIL;
    }
    else if (t.st_atime > t.st_mtime)
    { 
      /* mailbox read after most recent write */
      mailbox->status = OLD_MAIL;
    }
    else if (t.st_size > mailbox->size)
    {
      /* mailbox modified after most recent read, and larger in size */
      /* this implies the arrival of some new mails */

      if (mailbox->status == NEW_MAIL && always_new_mail_exec)
        *run |= True;
      else if (mailbox->status == NEW_MAIL)
        *run |= False;
      else
        *run |= True;

      *beep = True;

      mailbox->status = NEW_MAIL;
    }

    /* else no change */

    mailbox->size = t.st_size;                     /* record size of mailbox */
  }
  else
  {
    croak("cannot open mailbox \"%s\"; ignored", mailbox->name);
    wfree(mailbox_path);
    return False;
  }

  *redraw |= (prev_status != mailbox->status);

  mailbox->last_update = time(NULL);

  wfree(mailbox_path);
  return True;
}


static int get_new_count(Mailbox *mailbox, int *beep, int *redraw, int *run)
{
  proplist_t  path;
  char       *mailbox_path;
  int         prev_status,
              prev_new_mail_count;
  
  prev_status = mailbox->status;
  prev_new_mail_count = mailbox->new_mail_count;
        
  GET_ENTRY(mailbox->options, path, "Path");
  if (path == NULL)
  {
    croak("mailbox \"%s\" missing option \"Path\"; ignored", mailbox->name);
    return False;
  }
  else if (!PLIsString(path))
  {
    croak("mailbox \"%s\" has invalid path; ignored", mailbox->name);
    return False;
  }
  else
    mailbox_path = expand_path(PLGetString(path));

  if (count_mail(mailbox, mailbox_path))
  {
    if (mailbox->total_mail_count == 0)
    {
      /* there is no mail in the mailbox */
      mailbox->status = NO_MAIL;
    }
    else if (mailbox->new_mail_count == 0)
    {
      /* there are no new mails */
      mailbox->status = OLD_MAIL;
    }
    else if (mailbox->new_mail_count > prev_new_mail_count)
    {
      /* new mails have arrived! */
      if (mailbox->status == NEW_MAIL && always_new_mail_exec)
        *run |= True;
      else if (mailbox->status == NEW_MAIL)
        *run |= False;
      else
        *run |= True;

      *beep = True;

      mailbox->status = NEW_MAIL;
    }

    /* else no change */
  }
  else
  {
    croak("cannot open mailbox \"%s\"; ignored", mailbox->name);
    wfree(mailbox_path);
    return False;
  }

  *redraw |= (prev_status != mailbox->status);

  wfree(mailbox_path);
  return True;
}


static int count_mail(Mailbox *mailbox, char *mailbox_path)
{
  proplist_t     has_internal_data;
  FILE          *file = NULL;
  struct stat    t;
  struct utimbuf xtime;
  char           buf[13];
  int            end_of_header = False,
                 is_old        = False;
  int            m = 0,                     /* mail_count */
                 n = 0;                     /* new_mail_count */

#ifdef DEBUG
  croak("%s: %s", mailbox->name, mailbox_path);
#endif

  GET_ENTRY(mailbox->options, has_internal_data, "MailboxHasInternalData");

  /* only count the mails in the mailbox if it was modified after last update */
  if (!stat(mailbox_path, &t) && t.st_ctime <= mailbox->last_update)
#ifdef DEBUG
  {
    croak("ctime: %d last_update: %d", t.st_ctime, mailbox->last_update);
    return True;
  }
#else
    return True;
#endif

  /* save atime */
  xtime.actime = t.st_atime;

  if ((file = fopen(mailbox_path, "r")) == NULL)
    return False;

  while (fgets(buf, 12, file) != NULL)
  {
    if (!strncmp(buf, "From ", 5))
    {
      end_of_header = False;
      m++; 

      /* do not increment n if the _previous_ mail was already read */
      /* always assume the first mail has not been read */
      if (is_old)
        is_old = False;
      else
        n++;
    }
    else if (!strcmp(buf, "\n"))
    {
      /* the first blank line marks the end of headers */
      end_of_header = True;
    }
    else if (!strncmp(buf, "Status:", 7) && index(buf + 7, 'R'))
    {
      /* set is_old to True only if we are still reading
         the headers; otherwise, is_old is not affected */
      is_old |= !end_of_header;
    }

    if (buf[strlen(buf) - 1] != '\n')
    {
      /* skip everything until the next newline */
      int i;

      for (i = '\0'; i != '\n' && i != EOF; i = fgetc(file));
    }
  }

  /* must correct the obiwan error by decrementing n if the last mail is old */
  if (is_old)
    n--;

  /* if an mbox file contains a dummy message, discount it */
  if ( has_internal_data != NULL && PLIsString(has_internal_data) &&
       !strcasecmp(PLGetString(has_internal_data), "Yes") )
    m--;
  else if ( has_internal_data != NULL && (!PLIsString(has_internal_data) ||
            ( strcasecmp(PLGetString(has_internal_data), "Yes") &&
              strcasecmp(PLGetString(has_internal_data), "No") )) )
  {
    proplist_t label;

    croak("boolean expected for key \"MailboxHasInternalData\"; "
          "\"No\" assumed");

    label = PLMakeString("MailboxHasInternalData");
    has_internal_data = PLMakeString("No");
    PLInsertDictionaryEntry(mailbox->options, label, has_internal_data);
    PLRelease(has_internal_data);
    PLRelease(label);
  }

  fclose(file);

  stat(mailbox_path, &t);
  xtime.modtime = t.st_mtime;    /* save mtime */
  utime(mailbox_path, &xtime);   /* reset atime and mtime */

  mailbox->last_update = time(NULL);

  mailbox->total_mail_count = m;
  mailbox->new_mail_count = n;

#ifdef DEBUG
  croak("mail count: %d/%d",
        mailbox->new_mail_count, mailbox->total_mail_count);
#endif
  return True;
}

#endif


syntax highlighted by Code2HTML, v. 0.9.1