/* * 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 #include #include #include #include #include #include #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