This is Wais.m in view mode; [Download] [Up]
// Wais.m
//
// Free software created 1 Feb 1992
// by Paul Burchard <burchard@math.utah.edu>.
// Incorporating:
/*
WIDE AREA INFORMATION SERVER SOFTWARE:
No guarantees or restrictions. See the readme file for the full standard
disclaimer.
This is part of the [NeXTstep] user-interface for the WAIS software.
Do with it as you please.
Version 0.82
Wed Apr 24 1991
jonathan@Think.COM
*/
//
// Here are the general utility hacks that didn't belong anywhere else...
// See Wais.h for more information.
#import "Wais.h"
// These global vars are needed by the WAIS library.
// We also use them for non-NeXTstep error messages.
FILE *logfile = stderr;
char *log_file_name = NULL;
// List of all Wais objects and their keys.
static id convertKeyToObject;
static id globalObjectList;
// Search path for objects---subclass should override.
static id globalFolderList;
// Global NeXTstep error message table for Wais classes.
static BOOL waisIsQuiet = NO;
static id waisStringTable = nil;
// Locks for thread collision protection.
#ifdef WAIS_THREAD_SUPPORT
static mutex_t waisTransactionMutex;
static mutex_t waisFileIOMutex;
// Port for callback to main thread.
port_t waisCallbackPort = PORT_NULL;
typedef struct
{
msg_header_t hdr;
msg_type_t type1;
id callThis;
msg_type_t type2;
SEL performThis;
msg_type_t type3;
id withThis;
}
_WaisCallback, *WaisCallback;
#endif
@implementation Wais
void waisCallbackHandler(WaisCallback msg, void *userData)
{
[msg->callThis perform:msg->performThis with:msg->withThis];
return;
}
+ initialize
{
char *buf;
const char *home, *folder;
id folderList;
if(self == [Wais class])
{
// Grand unified indexed list of Wais objects.
globalObjectList = [[List alloc] init];
convertKeyToObject = [[HashTable alloc] initKeyDesc:"%" valueDesc:"@"];
#ifdef WAIS_THREAD_SUPPORT
// Locks to prevent threads from colliding.
waisTransactionMutex = mutex_alloc();
waisFileIOMutex = mutex_alloc();
// Callback port so other threads can ask main thread to
// perform thread-unsafe actions. This is done by registering
// the port with the application; the application's main thread
// picks up the requests via the AppKit event loop. We give
// the port a high priority since its cargo may be error messages.
port_allocate(task_self(), &waisCallbackPort);
DPSAddPort(waisCallbackPort, (DPSPortProc)waisCallbackHandler,
sizeof(_WaisCallback), NULL, NX_RUNMODALTHRESHOLD);
#endif
}
// Create subclass-specific list of folders in which
// the Wais objects will be found.
folderList = [[Storage alloc] initCount:0
elementSize:sizeof(NXAtom) description:"%"];
if((home=NXHomeDirectory()) && [self defaultHomeFolder])
{
if(!(buf = s_malloc(strlen(home)
+ strlen([self defaultHomeFolder]) + 1))) return nil;
strcpy(buf, home);
strcat(buf, [self defaultHomeFolder]);
folder = NXUniqueString(buf);
s_free(buf);
}
else folder = NXUniqueString("/");
[folderList insert:(void *)&folder at:0];
if(![self setFolderList:folderList]) return nil;
return self;
}
+ folderList
{
// Recommended subclass method.
return globalFolderList;
}
+ setFolderList:aList
{
// Recommended subclass method.
if(![aList isKindOf:[Storage class]]) return nil;
if([aList count] <= 0) return nil;
if(globalFolderList) [globalFolderList free];
globalFolderList = aList;
return self;
}
+ (const char *)defaultHomeFolder
{
// Recommended subclass method.
return "/Library/WAIS";
}
- (const char *)valueForStringKey:(const char *)aKey
{
if(!aKey) return 0;
return (const char *)[infoFields valueForKey:(void *)aKey];
}
- (const char *)insertStringKey:(const char *)aKey value:(const char *)aValue
{
const char *saveKey, *saveValue;
if(!aKey) return 0;
if(!aValue) { [infoFields removeKey:(void *)aKey]; return 0; }
saveKey = NXUniqueString(aKey); saveValue = NXUniqueString(aValue);
return (const char *)[infoFields
insertKey:(void *)saveKey value:(void *)saveValue];
}
+ waisObjectList
{
return globalObjectList;
}
+ objectForCompleteKey:(const char *)aKey
{
id found;
BOOL quiet;
// Is this a known, loaded object, of right type?
if(aKey[0] != '/') return nil;
if(found = (id)[convertKeyToObject valueForKey:(void *)aKey])
{
if([found isKindOf:[self class]]) return found;
else return nil;
}
// If unknown, try to load it as a file.
// (Suppress error msgs---just testing.)
found = [[[self class] alloc] initKey:aKey];
quiet = [[self class] isQuiet]; [[self class] setQuiet:YES];
if([found readWaisFile]) { [[self class] setQuiet:quiet]; return found; }
[[self class] setQuiet:quiet]; [found free];
return nil;
}
+ objectForKey:(const char *)aKey
{
id found, folderList;
const char **folders;
char *fullKey;
int f, len, nfold;
// If complete key, just look up.
if(!aKey) return nil;
if(aKey[0] == '/')
return [[self class] objectForCompleteKey:aKey];
// If partial key, try prepending all folders in folderList.
if(!(folderList=[[self class] folderList])
|| (nfold=[folderList count])<=0) return nil;
if(!(folders=(const char **)[folderList elementAt:0]))
return nil;
for(f=0; f<nfold; f++)
{
if(!folders[f]) continue;
len = strlen(folders[f]);
fullKey = s_malloc(len + strlen(aKey) + 2);
strcpy(fullKey, folders[f]);
if(fullKey[len-1] != '/') strcat(fullKey, "/");
strcat(fullKey, aKey);
if(found = [[self class] objectForCompleteKey:fullKey])
{ s_free(fullKey); return found; }
s_free(fullKey);
}
// Not found.
return nil;
}
- (const char *)key
{
return key;
}
// Note: in index, this simply overwrites any previous object with same key.
// Is this what we want?
- setKey:(const char *)aKey
{
id folderList;
const char **folders;
char *buf;
int len;
// Make sure it's in the list.
[globalObjectList addObjectIfAbsent:self];
// Remove object from index if new key is NULL.
if(!aKey)
{
if(key && self==(id)[convertKeyToObject valueForKey:(void *)key])
[convertKeyToObject removeKey:key];
key = 0;
return self;
}
// If full key, unique it and index it (removing any old entry).
if(aKey[0] == '/')
{
if(key && self==(id)[convertKeyToObject valueForKey:(void *)key])
[convertKeyToObject removeKey:key];
key = NXUniqueString(aKey);
[convertKeyToObject insertKey:(void *)key value:(void*)self];
return self;
}
// If partial key, first create full key using default folder.
if(!(folderList=[[self class] folderList]) || [folderList count]<=0)
return nil;
if(!(folders=(const char **)[folderList elementAt:0]))
return nil;
if(!folders[0]) return nil;
len = strlen(folders[0]);
buf = s_malloc(len + strlen(aKey) + 2);
strcpy(buf, folders[0]);
if(buf[len-1] != '/') strcat(buf, "/");
strcat(buf, aKey);
// Now index it (removing any old entry).
if(key && self==(id)[convertKeyToObject valueForKey:(void *)key])
[convertKeyToObject removeKey:key];
key = NXUniqueString(buf);
s_free(buf);
[convertKeyToObject insertKey:(void *)key value:(void*)self];
return self;
}
- initKey:(const char *)aKey
{
[super init];
key = 0;
[self setKey:aKey];
infoFields = [[HashTable alloc] initKeyDesc:"%" valueDesc:"%"];
return self;
}
- free
{
// Remove self from indices.
[convertKeyToObject removeKey:key];
[globalObjectList removeObject:self];
// Free instance vars (but don't free key---it's an NXAtom).
[infoFields free];
return [super free];
}
- (short)readWaisStruct:(const char *)structName
forElement:(const char *)elementName
fromFile:(FILE *)file
withDecoder:(WaisDecoder)theDecoder
{
short check_result;
static char read_buf[READ_BUF_SIZE];
char field_name[STRINGSIZE+MAX_SYMBOL_SIZE];
WaisDecoder decoder;
if(!structName || !theDecoder || !file) return FALSE;
[Wais lockFileIO];
if(feof(file)) { [Wais unlockFileIO]; return END_OF_STRUCT_OR_LIST; }
check_result = CheckStartOfStruct(structName+1, file);
if(check_result==FALSE || check_result==END_OF_STRUCT_OR_LIST)
{ [Wais unlockFileIO]; return check_result; }
while((check_result=ReadSymbol(field_name, file, MAX_SYMBOL_SIZE))!=FALSE
&& check_result!=END_OF_STRUCT_OR_LIST
&& !feof(file))
{
for(decoder=theDecoder; decoder->name; decoder++)
if(0 == strcmp(field_name, decoder->name))
{
switch(decoder->elementType)
{
case W_STRUCT:
[Wais unlockFileIO];
check_result = [self readWaisStruct:decoder->structName
forElement:decoder->name fromFile:file
withDecoder:decoder->subDecoder];
if(check_result==FALSE
|| check_result==END_OF_STRUCT_OR_LIST)
return check_result;
[Wais lockFileIO];
break;
case W_LIST:
if((check_result=ReadStartOfList(file))==FALSE
|| check_result==END_OF_STRUCT_OR_LIST)
{ [Wais unlockFileIO]; return check_result; }
[Wais unlockFileIO];
do check_result = [self
readWaisStruct:decoder->structName
forElement:decoder->name fromFile:file
withDecoder:decoder->subDecoder];
while(check_result!=FALSE
&& check_result!=END_OF_STRUCT_OR_LIST);
if(check_result==FALSE) return check_result;
[Wais lockFileIO];
break;
case W_FIELD:
if(!decoder->reader)
{ SkipObject(file); break; }
switch(decoder->readArgs)
{
case 1:
check_result = decoder->reader(file);
break;
case 2:
check_result = decoder->reader(read_buf, file);
break;
case 3:
check_result = decoder->reader(read_buf, file,
MIN(READ_BUF_SIZE, decoder->maxBufSize));
break;
default:
[Wais unlockFileIO]; return FALSE;
}
if(check_result==FALSE
|| check_result==END_OF_STRUCT_OR_LIST)
{ [Wais unlockFileIO]; return check_result; }
if(decoder->readArgs > 1)
[self insertStringKey:field_name value:read_buf];
break;
default:
[Wais unlockFileIO]; return FALSE;
}
break;
}
if(!decoder->name) SkipObject(file);
}
[Wais unlockFileIO];
return TRUE;
}
// Subclass method.
+ (const char *)fileStructName
{
return ":null";
}
// Subclass method.
+ (WaisDecoder)fileStructDecoder
{
return NULL;
}
- readWaisFile
{
FILE *file;
short check_result;
// Try to open file specified by key.
if(!key) return nil;
[Wais lockFileIO];
if(!(file = fopen(key, "r")))
{
[Wais unlockFileIO];
ErrorMsg([[self class] errorTitle], "Can't read %s file: %s.",
[[self class] fileStructName], key);
return nil;
}
[Wais unlockFileIO];
// Read using decoder.
check_result = [self readWaisStruct:[[self class] fileStructName]
forElement:[[self class] fileStructName]
fromFile:file
withDecoder:[[self class] fileStructDecoder]];
if(check_result == FALSE)
{
[Wais lockFileIO]; fclose(file); [Wais unlockFileIO];
ErrorMsg([[self class] errorTitle], "Bad %s file format: %s.",
[[self class] fileStructName], key);
return nil;
}
[Wais lockFileIO]; fclose(file); [Wais unlockFileIO];
return self;
}
// Recommended subclass method.
+ (BOOL)checkFileName:(const char *)fileName
{
return YES;
}
// User should free the returned List when done.
+ loadFolder:(const char *)folderName
{
int len;
id w, wlist;
char filename[MAX_FILENAME_LEN];
DIR *dirp;
struct dirent *dp;
// Open directory and loop through its files.
if(!folderName) return nil;
[Wais lockFileIO];
if(!(dirp = opendir(folderName)))
{
[Wais unlockFileIO];
ErrorMsg([[self class] errorTitle],
"Can't open folder %s.", folderName);
return nil;
}
wlist = [[List alloc] init];
while(dp=readdir(dirp))
{
// Check if file name has correct extension.
if(![[self class] checkFileName:dp->d_name]) continue;
// If yes, then read it in and put into return list.
strcpy(filename, folderName);
len = strlen(filename);
if(filename[len-1] != '/') strcat(filename, "/");
strcat(filename, dp->d_name);
w = [[[self class] alloc] initKey:filename];
[Wais unlockFileIO];
if([w readWaisFile]) [wlist addObject:w];
else [w free];
[Wais lockFileIO];
}
closedir(dirp);
[Wais unlockFileIO];
if([wlist count] <= 0) { [wlist free]; return nil; }
return wlist;
}
- (short)writeWaisStruct:(const char *)structName
forElement:(const char *)elementName
toFile:(FILE *)file
withDecoder:(WaisDecoder)theDecoder
{
short check_result;
const char *field_value;
WaisDecoder decoder;
if(!structName || !theDecoder || !file) return NO;
[Wais lockFileIO];
WriteStartOfStruct(structName+1, file);
WriteNewline(file);
for(decoder=theDecoder; decoder->name; decoder++)
switch(decoder->elementType)
{
case W_STRUCT:
WriteSymbol(decoder->name, file);
WriteNewline(file);
[Wais unlockFileIO];
check_result = [self writeWaisStruct:decoder->structName
forElement:decoder->name toFile:file
withDecoder:decoder->subDecoder];
if(check_result==FALSE) return check_result;
[Wais lockFileIO];
break;
case W_LIST:
WriteSymbol(decoder->name, file);
WriteNewline(file);
WriteStartOfList(file);
[Wais unlockFileIO];
do check_result = [self
writeWaisStruct:decoder->structName
forElement:decoder->name toFile:file
withDecoder:decoder->subDecoder];
while(check_result!=FALSE && check_result!=END_OF_STRUCT_OR_LIST);
if(check_result==FALSE) return check_result;
[Wais lockFileIO];
WriteNewline(file);
WriteEndOfList(file);
WriteNewline(file);
break;
case W_FIELD:
// Don't save empty fields.
if(!(field_value=[self valueForStringKey:decoder->name])) break;
if(!field_value[0]) break;
if(!decoder->writer) break;
WriteSymbol(decoder->name, file);
switch(decoder->writeArgs)
{
case 1:
check_result = decoder->writer(file);
break;
case 2:
check_result = decoder->writer(field_value, file);
break;
default:
[Wais unlockFileIO]; return FALSE;
}
//!!! The return values of the WriteXXX() routines from
//!!! the WAIS ir library cannot be trusted. They actually
//!!! use return values from fprintf(), which are undefined!
check_result = TRUE;
if(check_result==FALSE)
{ [Wais unlockFileIO]; return check_result; }
WriteNewline(file);
break;
default:
[Wais unlockFileIO]; return FALSE;
}
WriteEndOfStruct(file);
WriteNewline(file);
[Wais unlockFileIO];
return TRUE;
}
- writeWaisFile
{
FILE *file;
short check_result;
// Try to create file specified by key.
if(!key) return nil;
[Wais lockFileIO];
if(!(file = fopen(key, "w")))
{
[Wais unlockFileIO];
ErrorMsg([[self class] errorTitle], "Can't create %s file %s.",
[[self class] fileStructName], key);
return nil;
}
[Wais unlockFileIO];
// Write using decoder.
check_result = [self writeWaisStruct:[[self class] fileStructName]
forElement:[[self class] fileStructName]
toFile:file
withDecoder:[[self class] fileStructDecoder]];
if(check_result == FALSE)
{
[Wais lockFileIO]; fclose(file); [Wais unlockFileIO];
ErrorMsg([[self class] errorTitle], "Error writing %s file %s.",
[[self class] fileStructName], key);
return nil;
}
[Wais lockFileIO]; fclose(file); [Wais unlockFileIO];
return self;
}
+ lockTransaction
{
#ifdef WAIS_THREAD_SUPPORT
mutex_lock(waisTransactionMutex);
#endif
return self;
}
+ unlockTransaction
{
#ifdef WAIS_THREAD_SUPPORT
mutex_unlock(waisTransactionMutex);
cthread_yield();
#endif
return self;
}
+ lockFileIO
{
#ifdef WAIS_THREAD_SUPPORT
mutex_lock(waisFileIOMutex);
#endif
return self;
}
+ unlockFileIO
{
#ifdef WAIS_THREAD_SUPPORT
mutex_unlock(waisFileIOMutex);
cthread_yield();
#endif
return self;
}
+ waisNewLocks
{
// Locks to prevent threads from colliding.
//!!! We don't try to recycle old locks.
waisTransactionMutex = mutex_alloc();
waisFileIOMutex = mutex_alloc();
return self;
}
+ callback:anObject perform:(SEL)aSelector with:anArgument
{
#ifdef WAIS_THREAD_SUPPORT
_WaisCallback msg =
{
// Note we are just sending object id's and method SEL's.
{ 0, 0, sizeof(_WaisCallback), 0, PORT_NULL, waisCallbackPort, 0},
{ MSG_TYPE_INTEGER_32, 32, 1, TRUE, FALSE, FALSE },
anObject,
{ MSG_TYPE_INTEGER_32, 32, 1, TRUE, FALSE, FALSE },
aSelector,
{ MSG_TYPE_INTEGER_32, 32, 1, TRUE, FALSE, FALSE },
anArgument
};
msg_send(&(msg.hdr), 0, 10.0/*timeout*/);
#else
[anObject perform:aSelector with:anArgument];
#endif
return self;
}
+ (port_t)callbackPort
{
return waisCallbackPort;
}
+ setStringTable:aTable
{
waisStringTable = aTable;
return self;
}
+ setQuiet:(BOOL)yn
{
waisIsQuiet = yn;
return self;
}
+ (BOOL)isQuiet
{
return waisIsQuiet;
}
+ (const char *)errorTitle
{
// Recommended subclass method.
return "WAIS Error!";
}
// For internal use only! Use ErrorMsg() function instead.
+ popAlertPanel:errorBuf
{
char *title, *errmsg;
int title_len;
if(!errorBuf) return nil;
title = (char *)[errorBuf elementAt:0];
title_len = strlen(title);
errmsg = (char *)[errorBuf elementAt:title_len+1];
NXRunAlertPanel(title, errmsg,
[waisStringTable valueForStringKey:"OK"], NULL, NULL);
[errorBuf free];
return self;
}
@end
// -------- UTILITY ROUTINES FOR ERRORS AND WAIS FORMATTED I/O ----------
void ErrorMsg(const char *title, const char *format, ...)
{
va_list args;
id errorBuf;
int title_len;
if(waisIsQuiet) return;
// Create buffer for passing error title/message to callback.
errorBuf = [[Storage alloc] initCount:0 elementSize:sizeof(char)
description:"c"];
[errorBuf setNumSlots:CHARS_PER_PAGE];//!!!
va_start(args, format);
if(!errorBuf || !waisStringTable
|| ![waisStringTable valueForStringKey:format]
|| ![waisStringTable valueForStringKey:title])
{
// Can't use AppKit---enter message in log file instead.
[errorBuf free];
[Wais lockFileIO];
fprintf(logfile, "%s---", title);
vfprintf(logfile, format, args);
fprintf(logfile, "\n");
fflush(logfile);
[Wais unlockFileIO];
}
else
{
// Pop up alert panel with AppKit.
// Use callback since AppKit is not thread safe.
// +popAlertPanel: will free errorBuf when it's done with it.
sprintf((char *)[errorBuf elementAt:0],
"%s", [waisStringTable valueForStringKey:title]);
title_len = strlen((char *)[errorBuf elementAt:0]);
vsprintf((char *)[errorBuf elementAt:(title_len+1)],
[waisStringTable valueForStringKey:format], args);
[Wais callback:[Wais class]
perform:@selector(popAlertPanel:) with:errorBuf];
}
va_end(args);
return;
}
long ReadLongS(char *buffer, FILE *file)
{
long val, rtn;
rtn = ReadLong(file, &val);
if(!rtn || rtn==END_OF_STRUCT_OR_LIST) return(rtn);
sprintf(buffer, "%ld", val);
return(rtn);
}
long WriteLongS(char *buffer, FILE *file)
{
long val;
if(1 != sscanf(buffer, " %ld ", &val)) return FALSE;
WriteLong(val, file);
return TRUE;//!!!
}
long ReadDoubleS(char *buffer, FILE *file)
{
double val;
long rtn;
rtn = ReadDouble(file, &val);
if(!rtn || rtn==END_OF_STRUCT_OR_LIST) return(rtn);
sprintf(buffer, "%g", val);
return(rtn);
}
long WriteDoubleS(char *buffer, FILE *file)
{
double val;
if(1 != sscanf(buffer, " %le ", &val)) return FALSE;
WriteDouble(val, file);
return TRUE;//!!!
}
long ReadListX(FILE *file)
{
long rtn;
// Ignore list.
rtn = ReadStartOfList(file);
if(!rtn) return(rtn);
while(getc(file) != ')');
return(rtn);
}
void read_subfield(const char *source, char *key, char *value, int value_size)
{
char ch;
long position = 0; /* position in value */
const char *pos =strstr(source, key); /* address into source */
value[0] = '\0'; /* initialize to nothing */
if(NULL == pos)
return;
pos = pos + strlen(key);
ch = *pos;
/* skip leading quotes and spaces */
while((ch == '\"') || (ch == ' ')) {
pos++; ch = *pos;
}
for(position = 0; pos < source + strlen(source); pos++){
if((ch = *pos) == ' ') {
value[position] = '\0';
return;
}
value[position] = ch;
position++;
if(position >= value_size){
value[value_size - 1] = '\0';
return;
}
}
value[position] = '\0';
}
/* right now this hacks out the ^Q/S too. I'll do better later. --j */
void replace_controlM(char *buffer, long *length)
{
char *here, *there, c;
long i, newlength;
here = there = buffer;
for(newlength = 0, i = 0; i < *length; i++) {
c = *here;
switch (c) {
case 0:
*there = 0;
*length = newlength;
return;
case '\r':
*there = '\n';
newlength++;
here++; there++;
break;
case 19:
case 17:
here++;
break;
default:
*there = *here;
newlength++;
here++; there++;
}
}
*length = newlength;
}
any* copy_any(any *thing)
{
int i;
any* result;
result = NULL;
if(thing != NULL) {
if((result = (any*)s_malloc(sizeof(any))) != NULL) {
result->bytes = NULL;
result->size = thing->size;
if((result->bytes = s_malloc(thing->size)) != NULL) {
for(i = 0; i < thing->size; i++)
result->bytes[i] = thing->bytes[i];
}
}
}
return result;
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.