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.