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.