This is MailboxSummary.m in view mode; [Download] [Up]
/* -*-ObjC-*-
*******************************************************************************
*
* File: MailboxSummary.m
* RCS: MailboxSummary.m,v 1.15 1998/06/14 01:33:30 tom Exp
* Description:
* Author: Tom Hageman <tom@basil.icce.rug.nl>
* Created: October 1996
* Modified: April 1998, extracted from TransferPanel.
* Language: Objective-C
* Package: EnhanceMail
* Status: Experimental
*
* Copyright (C) 1996-1998 Tom Hageman, but otherwise this file is perfect freeware.
*
*******************************************************************************
*/
#import <libc.h>
#import <limits.h>
#define _MAILTOC_DEFINES 1
#import "mailtoc.h"
#import "MailboxSummary.h"
#import <objc/HashTable.h>
#import <defaults/defaults.h>
#import <appkit/nextstd.h>
#if DEBUG || DEBUG_SUMMARY
# define DFPRINTF(arglist) fprintf arglist
#else
# define DFPRINTF(arglist)
#endif
// Versioning support.
#define CLASS_NAME "EnhanceMailboxSummary"
#define CLASS_VERSION_0 0
#define CLASS_VERSION CLASS_VERSION_0
#define FILESTOSTAT MBOX_TOC_FILE
#define STAT_EQ_FILE(s1, s2) ((s1)->st_ino == (s2)->st_ino && \
((s1)->st_dev == (s2)->st_dev || \
/* Kludge to allow restoration from cache. */ \
(s1)->st_dev == 0 || (s2)->st_dev == 0))
#define STAT_EQ(s1, s2) (STAT_EQ_FILE((s1), (s2)) && \
(s1)->st_mtime == (s2)->st_mtime && \
(s1)->st_size == (s2)->st_size)
#define NEW_OR_UNREAD(array) ((array)[EMS_TAG_NEW] + (array)[EMS_TAG_UNREAD])
@implementation EnhanceMailboxSummary
#if (CLASS_VERSION != 0)
+ initialize
{
if (self == [EnhanceMailboxSummary class])
{
[self setVersion:CLASS_VERSION];
}
return self;
}
#endif
+ (const char *)getPath:(char *)buf forName:(const char *)name
{
int len;
if (*name == '/')
{
strcpy(buf, name);
}
else
{
if ((len = [self mailboxDir:buf]) < 0) return NULL;
buf[len++] = '/';
strcpy(buf+len, name);
}
len = strlen(buf);
if ((len < 5) || (strcmp(buf+len-5, ".mbox")) != 0) strcpy(buf+len, ".mbox");
return buf;
}
- initWithName:(const char *)name
{
if (self = [super init])
{
loadStatus = EMS_UNLOADED;
[self setName:name];
if (!fullname)
{
self = [self free];
}
}
return self;
}
- free
{
if (fullname)
{
[[self class] setSummary:nil forName:fullname];
free(fullname);
}
return [super free];
}
- (const char *)name { return fullname; }
- (void)setName:(const char *)name
{
if (name != fullname)
{
char path[MAXPATHLEN+1];
fullname = (free(fullname), NULL);
if ([[self class] getPath:path forName:name])
{
fullname = NXCopyStringBufferFromZone(path, [self zone]);
}
}
}
- (const char *)shortName
{
return [[self class] shortNameFor:fullname];
}
// Configuration.
- (void)setCollectAll:(BOOL)yn
{
if (yn && !collectAll) accurate = NO; // force reload.
collectAll = yn;
}
- (BOOL)collectAll { return collectAll; }
static BOOL doPendingAppnmailCheck = YES;
+ (BOOL)doPendingAppnmailCheck { return doPendingAppnmailCheck; }
+ (void)setDoPendingAppnmailCheck:(BOOL)value
{
doPendingAppnmailCheck = value;
}
static BOOL doInSyncCheck = YES;
+ (BOOL)doInSyncCheck { return doInSyncCheck; }
+ (void)setDoInSyncCheck:(BOOL)value
{
doInSyncCheck = value;
}
// Support methods
- (const char *)getPath:(char *)buf forFile:(const char *)file
{
sprintf(buf, "%s/%s", fullname, file);
return buf;
}
- (void)_reset
{
int tag = EMS_NUMTAGS;
while (--tag >= 0)
{
counts[tag] = 0;
sizes[tag] = 0;
attachCounts[tag] = 0;
attachSizes[tag] = 0;
}
}
- (enum EMSLoadStatus)_loadTocFrom:(off_t)seekOffset
{
char path[MAXPATHLEN+1];
FILE *tocf;
DFPRINTF((stderr, "-[%s %s]: (%s) offset=%ld loadStatus=%d\n", [[self class] name], SELNAME(_cmd), [self shortName], seekOffset, loadStatus));
loadStatus = EMS_UNLOADED;
accurate = NO;
if (seekOffset == 0) [self _reset];
if ((tocf = fopen([self getPath:path forFile:MBOX_TOC_FILE], "rb")) != NULL)
{
struct table_of_contents_header *th;
#if 0 /* Does not work since setvbuf is not in Mail.app's symbol table :~( */
// Enlarge stdio buffer size to read toc in one fell swoop.
# define MAX_BUFSIZ (((128 * 1024) + BUFSIZ - 1) / BUFSIZ * BUFSIZ)
size_t bufsiz = (stats[EMS_STAT_TOC].st_size + BUFSIZ - 1) / BUFSIZ * BUFSIZ;
if (bufsiz == 0) bufsiz = BUFSIZ;
else if (bufsiz > MAX_BUFSIZ) bufsiz = MAX_BUFSIZ;
setvbuf(tocf, NULL, _IOFBF, bufsiz);
#endif
if ((th = get_table_of_contents_header(tocf, NO)) == NULL)
{
[self _reset];
loadStatus = EMS_LOADFAILED;
}
else
{
int msgCount;
// Assume success.
loadStatus = EMS_LOADED;
accurate = YES;
if (doInSyncCheck)
{
// For consistency, check if mbox timestamp is in sync.
struct stat mbox_stat;
if (stat([self getPath:path forFile:MBOX_CONTENTS_FILE], &mbox_stat) < 0 ||
th->mbox_time != mbox_stat.st_mtime)
{
loadStatus = EMS_LOADFAILED;
accurate = NO;
}
}
restart:
msgCount = th->num_msgs;
if (seekOffset != 0)
{
if ((msgCount -= counts[EMS_TAG_TOTAL]) < 0)
{
seekOffset = 0;
goto restart; // Ho. Hum.
}
fseek(tocf, seekOffset, SEEK_SET);
}
// Tally read, new and deleted messages.
while (--msgCount >= 0)
{
struct message_index *mi;
int tag;
if ((mi = get_message_index(tocf)) == NULL)
{
if (seekOffset != 0 && !ferror(tocf))
{
// Non-fatal error: retry reading toc from the start.
seekOffset = 0;
fseek(tocf, sizeof(*th), SEEK_SET);
[self _reset];
goto restart; // See? I told you it was getting messy...
}
loadStatus = EMS_LOADFAILED;
accurate = NO;
break;
}
switch (mi->status)
{
case MT_STATUS_NEW:
tag = EMS_TAG_NEW;
break;
case MT_STATUS_DELETED_1:
case MT_STATUS_DELETED_2:
tag = EMS_TAG_DELETED;
break;
case MT_STATUS_UNREAD_1:
case MT_STATUS_UNREAD_2:
tag = EMS_TAG_UNREAD;
break;
case MT_STATUS_FLAGGED:
tag = EMS_TAG_FLAGGED;
break;
default:
tag = EMS_TAG_READ;
break;
}
counts[tag]++;
counts[EMS_TAG_TOTAL]++;
if (collectAll)
{
int size = mi->mes_length;
int attachSize = message_attachsize(mi);
if (attachSize > 0)
{
attachCounts[tag]++;
attachSizes[tag] += attachSize;
attachCounts[EMS_TAG_TOTAL]++;
attachSizes[EMS_TAG_TOTAL] += attachSize;
size += attachSize;
}
sizes[tag] += size;
sizes[EMS_TAG_TOTAL] += size;
}
free(mi);
}
// We should be at end-of-file by now, otherwise TOC is inconsistent.
/* {{Disabled for now since outside agent may have appended new
messages in the meantime.}} */
///if (getc(tocf) != EOF) loadStatus = EMS_LOADFAILED;
free(th);
}
fclose(tocf);
}
return loadStatus;
}
- (enum EMSLoadStatus)load
{
struct stat oldstat = stats[EMS_STAT_TOC]; // XXX Ugh!
BOOL shouldLoad = [self shouldLoad]; // XXX Knows this reloads stats[].
// Optimization: start reading at previous EOF if TOC grew.
BOOL seekToEnd = (loadStatus == EMS_LOADED && accurate &&
STAT_EQ_FILE(&oldstat, &stats[EMS_STAT_TOC]) &&
oldstat.st_mtime <= stats[EMS_STAT_TOC].st_mtime &&
oldstat.st_size < stats[EMS_STAT_TOC].st_size &&
oldstat.st_size > sizeof(struct table_of_contents_header));
// XXX Ugh galore! This is getting very messy indeed...
long oldNewOrUnread = (loadStatus == EMS_LOADED) ? NEW_OR_UNREAD(counts) : LONG_MAX;
// conditionally check for pending appnmail.
hasPendingAppnmail = (doPendingAppnmailCheck ? [self checkPendingAppnmail] : NO);
needsReload = NO;
if ([self suppressLoad])
{
[self _reset];
loadStatus = EMS_LOADSUPPRESSED;
accurate = NO;
hasNewMail = NO;
return loadStatus;
}
// Should also load if suppression status has changed.
if (!(shouldLoad || loadStatus == EMS_LOADSUPPRESSED))
{
return loadStatus;
}
if ([self _loadTocFrom:(seekToEnd ? oldstat.st_size : 0)] != EMS_LOADED)
{
hasNewMail = NO;
return loadStatus;
}
if (NEW_OR_UNREAD(counts) > oldNewOrUnread) hasNewMail = YES;
else if (NEW_OR_UNREAD(counts) < oldNewOrUnread) hasNewMail = NO;
return loadStatus;
}
- (enum EMSLoadStatus)loadStatus { return loadStatus; }
- (BOOL)isLoaded { return loadStatus != EMS_UNLOADED && loadStatus != EMS_LOADFAILED; }
- (BOOL)shouldLoad
{
char path[MAXPATHLEN+1];
struct stat newstats[EMS_NUMSTATS];
static const char * const filesToStat[EMS_NUMSTATS] = { FILESTOSTAT };
BOOL result = NO;
int i;
for (i = 0; i < EMS_NUMSTATS; i++)
{
memset(&newstats[i], 0, sizeof(struct stat));
stat([self getPath:path forFile:filesToStat[i]], &newstats[i]);
if (!STAT_EQ(&newstats[i], &stats[i]))
{
result = YES;
///break; // NOT!!! must stat all for accurate cache.
}
stats[i] = newstats[i];
}
return result;
}
- (BOOL)suppressLoad { return NO; }
- (BOOL)needsReload { return needsReload; }
- (void)setNeedsReload:(BOOL)value { needsReload = value; }
// General results.
- (long)count:(int)tag
{
NX_ASSERT((unsigned)tag < EMS_NUMTAGS, "tag < EMS_NUMTAGS");
return counts[tag];
}
- (unsigned long)size:(int)tag
{
NX_ASSERT((unsigned)tag < EMS_NUMTAGS, "tag < EMS_NUMTAGS");
return sizes[tag];
}
- (long)attachCount:(int)tag
{
NX_ASSERT((unsigned)tag < EMS_NUMTAGS, "tag < EMS_NUMTAGS");
return attachCounts[tag];
}
- (unsigned long)attachSize:(int)tag
{
NX_ASSERT((unsigned)tag < EMS_NUMTAGS, "tag < EMS_NUMTAGS");
return attachSizes[tag];
}
// Specialized results.
- (long)numUnreadMessages { return counts[EMS_TAG_UNREAD]; }
- (void)setUnreadMessages:(long)value { counts[EMS_TAG_UNREAD] = value; accurate = NO; hasNewMail = NO; }
- (long)numNewMessages { return counts[EMS_TAG_NEW]; }
- (void)setNewMessages:(long)value { counts[EMS_TAG_NEW] = value; accurate = NO; hasNewMail = NO; }
- (long)numFlaggedMessages { return counts[EMS_TAG_FLAGGED]; }
- (void)setFlaggedMessages:(long)value { counts[EMS_TAG_FLAGGED] = value; accurate = NO; }
- (long)numNewOrUnreadMessages { return NEW_OR_UNREAD(counts); }
- (BOOL)hasNewMail { return hasNewMail; }
- (void)setHasNewMail:(BOOL)value { hasNewMail = value; }
- (BOOL)hasIndex
{
char path[MAXPATHLEN+1];
return access([self getPath:path forFile:MBOX_INDEX_FILE], F_OK) == 0;
}
- (unsigned long)indexSize
{
char path[MAXPATHLEN+1];
struct stat st;
return (stat([self getPath:path forFile:MBOX_INDEX_FILE], &st) < 0) ? 0 : st.st_size;
}
- (unsigned long)tableOfContentsSize { return stats[EMS_STAT_TOC].st_size; }
// Appnmail extensions.
- (BOOL)hasPendingAppnmail { return hasPendingAppnmail; }
/* NB. This does not work if invoked from Mail.app itself... */
- (BOOL)incorporatePendingAppnmail:(BOOL)wait
{
char command[60 + 3*MAXPATHLEN + 1];
const char *s;
char *d = command;
for (s = "PATH=/usr/local/bin:$PATH; exec appnmail -i </dev/null '"; *s; ) *d++ = *s++;
// Escape single quote characters.
for (s = [self name]; *s; )
{
if ((*d++ = *s++) == '\'')
{
*d++ = '\\', *d++ = '\'', *d++ = '\'';
}
}
*d++ = '\'';
if (!wait) *d++ = '&';
*d = '\0';
return (system(command) == EXIT_SUCCESS);
}
#define APPNMAIL_TOC "appnmail_"MBOX_TOC_FILE
#define APPNMAIL_MBOX "appnmail_"MBOX_CONTENTS_FILE
- (BOOL)checkPendingAppnmail
{
char path[MAXPATHLEN+1];
hasPendingAppnmail = (access([self getPath:path forFile:APPNMAIL_MBOX], F_OK) == 0 &&
access([self getPath:path forFile:APPNMAIL_TOC], F_OK) == 0);
return hasPendingAppnmail;
}
@end // EnhanceMailboxSummary
// ripped off/adapted from mailapp-utilities/mailutil.c:
extern const char *NXHomeDirectory(void);
@implementation EnhanceMailboxSummary (PathUtils)
+ (int)mailboxDir:(char *)buf
{
int len;
const char *str;
if ((str=NXGetDefaultValue("Mail","MailDir")) != NULL)
{
strcpy(buf,str);
}
else
{
strcpy(buf,"~/Mailboxes");
}
if ((buf[0]=='~') && (buf[1]=='/'))
{
char temp[MAXPATHLEN];
if (!(str=NXHomeDirectory()) && !(str=getenv("HOME"))) return -1;
strcpy(temp,buf+1);
strcpy(buf,str);
strcat(buf,temp);
}
len=strlen(buf);
if (buf[len-1]=='/')
{
len--;
buf[len]='\0';
}
return len;
}
+ (const char *)shortNameFor:(const char *)path
{
char mailboxDir[MAXPATHLEN+1];
int len = [self mailboxDir:mailboxDir];
if (strncmp(mailboxDir, path, len) == 0)
{
switch (path[len])
{
case '/': path += len + 1; break;
case '\0': path += len; break;
default: break;
}
}
return path;
}
@end // EnhanceMailboxSummary (PathUtils)
@implementation EnhanceMailboxSummary (SummaryHash)
static HashTable *uniquehash = nil;
+ (void)setSummary:(EnhanceMailboxSummary *)sum forName:(const char *)name
{
if (!name) return;
if (sum)
{
if (!uniquehash) uniquehash = [[HashTable alloc] initKeyDesc:"*"];
[uniquehash insertKey:name value:sum];
}
else
{
[uniquehash removeKey:name];
}
}
+ summaryForName:(const char *)name create:(BOOL)create
{
char path[MAXPATHLEN+1];
id sum;
if (![self getPath:path forName:name]) return nil;
sum = [uniquehash valueForKey:path];
if (!sum && create)
{
// XXX handle symlinks to mboxes.
sum = [[self alloc] initWithName:path];
[self setSummary:sum forName:[sum name]];
}
return sum;
}
+ summaryForName:(const char *)name
{
return [self summaryForName:name create:YES];
}
@end // EnhanceMailboxSummary (SummaryHash)
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.