This is MailboxSummaryList.m in view mode; [Download] [Up]
/* -*-ObjC-*- ******************************************************************************* * * File: MailboxSummaryList.m * RCS: MailboxSummaryList.m,v 1.14 1998/06/30 20:16:51 tom Exp * Description: * Author: Tom Hageman <tom@basil.icce.rug.nl> * Created: April 1998 * Modified: * Language: Objective-C * Package: EnhanceMail * Status: Experimental * * Copyright (C) 1998 Tom Hageman, but otherwise this file is perfect freeware. * ******************************************************************************* */ #import "MailboxSummaryList.h" #import "MailboxSummary.h" #import "Enumerator.h" #import <objc/HashTable.h> #import <sys/types.h> #import <sys/dir.h> #import <appkit/nextstd.h> #import <appkit/Application.h> // For delayed perform. #if DEBUG || DEBUG_SUMMARY || DEBUG_ITERATE # define DFPRINTF(arglist) fprintf arglist #else # define DFPRINTF(arglist) #endif // Versioning support. #define CLASS_NAME "EnhanceMailboxSummaryList" #define CLASS_VERSION_0 0 #define CLASS_VERSION CLASS_VERSION_0 #define DONTSCAN_FILE ".dontscan" #define DOSCAN_FILE ".doscan" @interface EnhanceMailboxSummaryList (PrivateSummaryHash) + (void)setSummaryList:(EnhanceMailboxSummaryList *)list forName:(const char *)name; + _summaryListForName:(const char *)name; @end @implementation EnhanceMailboxSummaryList #if (CLASS_VERSION != 0) + initialize { if (self == [EnhanceMailboxSummaryList class]) { [self setVersion:CLASS_VERSION]; } return self; } #endif + (const char *)getPath:(char *)buf forName:(const char *)name { if (*name == '/') { strcpy(buf, name); } else { int len = [[EnhanceMailboxSummary class] mailboxDir:buf]; if (len < 0) return NULL; if (*name) { buf[len++] = '/'; strcpy(buf+len, name); } } return buf; } + summaryListForName:(const char *)name { return [self _summaryListForName:name]; } - initWithName:(const char *)name { if ((self = [super init]) != nil) { loadStatus = EMS_UNLOADED; [self setName:name]; if (!fullname) { [self free]; return nil; } [[self class] setSummaryList:self forName:fullname]; // record. } return self; } - empty { [self iterateAbort]; // Recursively free SummaryLists; Summaries are not freed. while ([self count] > 0) { id object = [self lastObject]; [self removeLastObject]; if ([object isKindOf:[EnhanceMailboxSummaryList class]]) { [object free]; } } return [super empty]; } - free { [self empty]; if (fullname) { [[self class] setSummaryList: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 [[EnhanceMailboxSummary class] shortNameFor:fullname]; } // Support. - (const char *)getPath:(char *)buf forFile:(const char *)file { sprintf(buf, "%s/%s", fullname, file); return buf; } - (void)_setBool:(BOOL*)valP file:(const char *)file to:(BOOL)aValue { if (aValue != (*valP)) { char path[MAXPATHLEN+1]; [self getPath:path forFile:file]; if (((*valP) = aValue)) { close(creat(path, 000)); } else { unlink(path); } } } // Access methods. - (BOOL)dontScan { return _dontScan; } - (BOOL)doScan { return _doScan; } - (void)setDontScan:(BOOL)aValue { [self _setBool:&_dontScan file:DONTSCAN_FILE to:aValue]; if (aValue) [self setDoScan:NO]; } - (void)setDoScan:(BOOL)aValue { [self _setBool:&_doScan file:DOSCAN_FILE to:aValue]; if (aValue) [self setDontScan:NO]; } - (BOOL)autoUpdateCache { return _autoUpdateCache; } - (void)setAutoUpdateCache:(BOOL)aValue { _autoUpdateCache = aValue; } // Loading. - (enum EMSLoadStatus)load { return [self loadUnless:_suppressed]; } - (enum EMSLoadStatus)loadUnless:(BOOL)suppressed { if (loadStatus == EMS_UNLOADED) [self scandir]; _suppressed = suppressed; if (loadStatus == EMS_LOADED || loadStatus == EMS_LOADSUPPRESSED) { suppressed = (suppressed || _dontScan) && !_doScan; [self makeObjectsPerform:_cmd with:(id)(int)suppressed]; loadStatus = (suppressed) ? EMS_LOADSUPPRESSED : EMS_LOADED; if (_autoUpdateCache) { [self storeToCache]; } } return loadStatus; } - (int)scandir { char path[MAXPATHLEN+1]; DIR *dir, *subdir; struct direct *d; int i, summaries = 0; [self empty]; if ((dir = opendir([self name])) == NULL) { loadStatus = EMS_LOADFAILED; return -1; } loadStatus = EMS_LOADED; _dontScan = _doScan = NO; while ((d = readdir(dir)) != NULL) { if (strcmp(d->d_name, ".") == 0) continue; if (strcmp(d->d_name, "..") == 0) continue; if (strcmp(d->d_name, DONTSCAN_FILE) == 0) { _dontScan = YES; continue; } if (strcmp(d->d_name, DOSCAN_FILE) == 0) { _doScan = YES; continue; } [self getPath:path forFile:d->d_name]; if (d->d_namlen > 5 && strcmp(&d->d_name[d->d_namlen-5], ".mbox") == 0) { [self addObject:[EnhanceMailboxSummary summaryForName:path]]; ++summaries; } else if ((subdir = opendir(path)) != NULL) { closedir(subdir); [self insertObject:[[[self class] allocFromZone:[self zone]] initWithName:path] at:0]; } } closedir(dir); // Recurse into subdirectories. i = [self count]; while (--i >= 0) { id object = [self objectAt:i]; if ([object respondsTo:_cmd]) { int result = [object scandir]; if (result < 0) { [self removeObjectAt:i]; } else { summaries += result; } } } return summaries; } - (enum EMSLoadStatus)loadStatus { return loadStatus; } - (BOOL)isLoaded { return loadStatus == EMS_LOADED; } - (BOOL)isLoadSuppressed { return loadStatus == EMS_LOADSUPPRESSED; } // General results. - (unsigned long)_countPerform:(SEL)sel tag:(int)tag { int i = [self count]; int total = 0; while (--i >= 0) { total += (unsigned long)[[self objectAt:i] perform:sel with:(id)tag]; } return total; } - (long)count:(int)tag { return [self _countPerform:_cmd tag:tag]; } - (unsigned long)size:(int)tag { return [self _countPerform:_cmd tag:tag]; } - (long)attachCount:(int)tag { return [self _countPerform:_cmd tag:tag]; } - (unsigned long)attachSize:(int)tag { return [self _countPerform:_cmd tag:tag]; } // Specialized results. - (long)numUnreadMessages { return [self _countPerform:_cmd tag:0]; } - (long)numNewMessages { return [self _countPerform:_cmd tag:0]; } - (int)numMailboxesWithNewOrUnreadMessages { return [self _countPerform:_cmd tag:0]; } - (BOOL)hasPendingAppnmail { int i = [self count]; while (--i >= 0) { if ([[self objectAt:i] hasPendingAppnmail]) return YES; } return NO; } - (BOOL)hasNewMail { int i = [self count]; while (--i >= 0) { if ([[self objectAt:i] hasNewMail]) return YES; } return NO; } - (int)numMailboxesWithNewMail { return [self _countPerform:_cmd tag:0]; } @end // EnhanceMailboxSummaryList @implementation EnhanceMailboxSummaryList (PrivateSummaryHash) static HashTable *uniquehash = nil; + (void)setSummaryList:(EnhanceMailboxSummaryList *)list forName:(const char *)name { if (!name) return; if (list) { if (!uniquehash) uniquehash = [[HashTable alloc] initKeyDesc:"*"]; [uniquehash insertKey:name value:list]; } else { [uniquehash removeKey:name]; } } + _summaryListForName:(const char *)name { char path[MAXPATHLEN+1]; if (![self getPath:path forName:name]) return nil; return [uniquehash valueForKey:path]; } @end // EnhanceMailboxSummaryList (PrivateSummaryHash) @implementation EnhanceMailboxSummaryList (Archiving) - read:(NXTypedStream *)stream { char *shortName; [super read:stream]; NXReadType(stream, @encode(typeof(shortName)), &shortName); [self setName:shortName]; free(shortName); NXReadType(stream, @encode(typeof(loadStatus)), &loadStatus); NXReadType(stream, @encode(typeof(_dontScan)), &_dontScan); NXReadType(stream, @encode(typeof(_doScan)), &_doScan); NXReadType(stream, @encode(typeof(_suppressed)), &_suppressed); if (!fullname) { self = [self free]; } else { [[self class] setSummaryList:self forName:fullname]; // record. } return self; } - write:(NXTypedStream *)stream { const char *shortName = [self shortName]; [super write:stream]; NXWriteType(stream, @encode(typeof(shortName)), &shortName); NXWriteType(stream, @encode(typeof(loadStatus)), &loadStatus); NXWriteType(stream, @encode(typeof(_dontScan)), &_dontScan); NXWriteType(stream, @encode(typeof(_doScan)), &_doScan); NXWriteType(stream, @encode(typeof(_suppressed)), &_suppressed); return self; } @end // EnhanceMailboxSummaryList (Archiving) #define CACHE_FILE ".enhanceSummaryCache" @implementation EnhanceMailboxSummaryList (Caching) static time_t lastModTime; // XXX quick hack; should be instance variable. - (time_t)modTime { time_t result = 0; int i = [self count]; while (--i >= 0) { time_t time = [[self objectAt:i] modTime]; if (time > result) result = time; } return result; } + cachedSummaryListForName:(const char *)name { id result = nil; char cachePath[MAXPATHLEN+1]; [self getPath:cachePath forName:name]; sprintf(cachePath + strlen(cachePath), "/%s", CACHE_FILE); NX_DURING { NXTypedStream *stream; if ((stream = NXOpenTypedStreamForFile(cachePath, NX_READONLY)) != NULL) { DFPRINTF((stderr, "+[%s %s]: loading \"%s\"...\n", [self name], SELNAME(_cmd), cachePath)); result = NXReadObject(stream); NXCloseTypedStream(stream); DFPRINTF((stderr, "+[%s %s]: loading \"%s\"...done\n", [self name], SELNAME(_cmd), cachePath)); } lastModTime = [result modTime]; } NX_HANDLER { fprintf(stderr, "%s cannot load from \"%s\", reason: %s (exception %d)\n", SELNAME(_cmd), cachePath, (char *)NXLocalHandler.data1, NXLocalHandler.code); } NX_ENDHANDLER return result; } + (BOOL)storeSummaryListToCache:(EnhanceMailboxSummaryList *)list { BOOL result = NO; char cachePath[MAXPATHLEN+1]; if (!list) return result; [list getPath:cachePath forFile:CACHE_FILE]; NX_DURING { NXTypedStream *stream; // XXX Race conditons? ///unlink(cachePath); if ((stream = NXOpenTypedStreamForFile(cachePath, NX_WRITEONLY)) != NULL) { DFPRINTF((stderr, "+[%s %s]: writing \"%s\"...\n", [self name], SELNAME(_cmd), cachePath)); NXWriteRootObject(stream, list); NXCloseTypedStream(stream); DFPRINTF((stderr, "+[%s %s]: writing \"%s\"...done\n", [self name], SELNAME(_cmd), cachePath)); result = YES; } } NX_HANDLER { fprintf(stderr, "%s cannot store to \"%s\", reason: %s (exception %d)\n", SELNAME(_cmd), cachePath, (char *)NXLocalHandler.data1, NXLocalHandler.code); } NX_ENDHANDLER return result; } - (BOOL)storeToCache { time_t modTime = [self modTime]; DFPRINTF((stderr, "-[%s %s] (%s) modTime = %s", [[self class] name], SELNAME(_cmd), [self name], ctime(&modTime))); if (modTime <= lastModTime) return YES; // up-to-date; deemed successful. lastModTime = modTime; return [[self class] storeSummaryListToCache:self]; } @end // EnhanceMailboxSummaryList (Caching) @implementation EnhanceMailboxSummaryList (IterateLoad) static int _iterateLoadInterval = 500; // Load interval in ms. + (int)iterateLoadInterval { return _iterateLoadInterval; } + (void)setIterateLoadInterval:(int)value { _iterateLoadInterval = value; } - (void)_iterateLoad { for (;;) { id object = [iterateLoadEnumerator nextObject]; int st; if (object == nil) { // Just to be sure... /// [self perform:_cmd with:nil afterDelay:-1 cancelPrevious:YES]; // Disabled to (hopefully) avoid NS3.3 crashes. if ([iterateLoadContext respondsTo:@selector(didFinishIterateLoad:)]) { [iterateLoadContext perform:@selector(didFinishIterateLoad:) with:self]; } iterateLoadContext = nil; iterateLoadEnumerator = nil; if (_autoUpdateCache) { [self storeToCache]; } return; } st = [object iterateLoad:self]; /* suspend iteration; presumably objects context (which should be us) picks it up in -didFinishIterateLoad: */ if (st < 0) return; /* did something: delay next iteration. */ if (st > 0 && _iterateLoadInterval >= 0) break; /* did nothing; try next one immediately. */ } [self perform:_cmd with:nil afterDelay:_iterateLoadInterval cancelPrevious:YES]; } - (int)iterateLoad:sender { // Refuse to do anything if previous cycle is still running. if ([self iterating]) return 0; DFPRINTF((stderr, "-[%s %s] (%s)\n", [[self class] name], SELNAME(_cmd), [self shortName])); if (loadStatus == EMS_UNLOADED) [self scandir]; if (loadStatus != EMS_LOADED && loadStatus != EMS_LOADSUPPRESSED) return 0; iterateLoadContext = sender; iterateLoadEnumerator = [self enhanceReverseObjectEnumerator]; if ([sender respondsTo:@selector(isLoadSuppressed)]) { _suppressed = [sender isLoadSuppressed]; } loadStatus = ((_suppressed || _dontScan) && !_doScan) ? EMS_LOADSUPPRESSED : EMS_LOADED; [self _iterateLoad]; return -1; // suspend parent iteration. } - (void)didFinishIterateLoad:(EnhanceMailboxSummaryList *)sender { // resume suspended iteration. DFPRINTF((stderr, "-[%s %s] (sender = %s)\n", [[self class] name], SELNAME(_cmd), [sender shortName])); [self _iterateLoad]; } - (void)newMailForSummary:(EnhanceMailboxSummary *)sender { // pass it on to parent context. if ([iterateLoadContext respondsTo:_cmd]) { [iterateLoadContext newMailForSummary:sender]; } } - (BOOL)iterating { return (iterateLoadEnumerator != nil); } - (void)iterateAbort { if (![self iterating]) return; DFPRINTF((stderr, "-[%s %s] (%s)\n", [[self class] name], SELNAME(_cmd), [self shortName])); // Cancel outstanding delayed-perform (if any). [self perform:@selector(_iterateLoad) with:nil afterDelay:-1 cancelPrevious:YES]; iterateLoadEnumerator = [iterateLoadEnumerator free]; iterateLoadContext = nil; // ...recursively. [self makeObjectsPerform:_cmd]; } @end // EnhanceMailboxSummaryList (IterateLoad) @implementation EnhanceMailboxSummary (SummaryListExtensions) - (int)numMailboxesWithNewOrUnreadMessages { return ([self numNewOrUnreadMessages] > 0 ? 1 : 0); } - (int)numMailboxesWithNewMail { return [self hasNewMail]; } - (enum EMSLoadStatus)loadUnless:(BOOL)suppressed { if (!suppressed) return [self load]; [self setNeedsReload:YES]; return loadStatus; } - (int)iterateLoad:(EnhanceMailboxSummaryList *)sender { if (![sender isLoadSuppressed]) { BOOL hadNewMail = [self hasNewMail]; DFPRINTF((stderr, "-[%s %s] (%s)\n", [[self class] name], SELNAME(_cmd), [self shortName])); [self load]; if (!hadNewMail && [self hasNewMail]) { [sender newMailForSummary:self]; } return 1; } DFPRINTF((stderr, "-[%s %s] (%s) load suppressed\n", [[self class] name], SELNAME(_cmd), [self shortName])); [self setNeedsReload:YES]; return 0; } - (time_t)modTime { return stats[EMS_STAT_TOC].st_mtime; } - (void)iterateAbort {} @end // EnhanceMailboxSummary (SummaryListExtensions)
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.