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.