ftp.nice.ch/pub/next/connectivity/infosystems/WAIStation.1.9.6.N.b.tar.gz#/WAIS/next-ui/WAISControl.m

This is WAISControl.m in view mode; [Download] [Up]

// WAISControl.m
//
// Free software created 1 Feb 1992
// by Paul Burchard <burchard@math.utah.edu>.

#import "WAISControl.h"
#import "Wais.h"
#import "QuestionDoc.h"
#import "SourceDoc.h"
#import "BrowserPane.h"
#import "IconWellControl.h"
#import "MailSpeaker.h"
#import <appkit/appkit.h>
#import <sys/file.h>


@implementation WAISControl


// ----------------------- PREFERENCES ------------------------ //


static NXDefaultsVector WAIStationDefaults =
{
    { "SystemFolder", "/LocalLibrary/WAIS" },
    { "SystemSourcesFolder", "/LocalLibrary/WAIS/sources" },
    { "UserFolder", "~/Library/WAIS" },
    { "UserSourcesFolder", "~/Library/WAIS/sources" },
    { "UserDocumentsFolder", "~/Library/WAIS/documents" },
    { "UserQuestionsFolder", "~/Library/WAIS/questions" },
    { "OpenOnRetrieval", "YES" },
    { "DocumentTypes", "" },
    { "MaxSearchResults", "30" },
    { "MailListAddress", "listserv@think.com" },
    { "MailListMessage", "ADD wais-talk\nADD wais-interest\nADD wais-discussion\n" },
    { "BugReportAddress", "burchard@math.utah.edu" },
    { "BugReportCC", "bug-wais@think.com" },
    { "BugReportSubject", "Comments on WAIStation.app " WAISTATION_VERSION " for NeXT" },
    { "BugReportMessage", "Please help us in making the WAIS software better.  
Please answer candidly.

How was the installation on the NeXT?
How is the documentation?
Comments on the server code:
Comments on the NeXT interface:
Comments on the existing WAIS sources on the net:
What WAIS sources would you like to see:
Would you like to help?  In what way?
General comments:
" },
    { NULL }
};

+ initialize
{
    NXRegisterDefaults("WAIStation", WAIStationDefaults);
    return self;
}

// Returns NXAtom-ized version of full folder name.
// Adds this name to the search path for the WaisClass (if not already there).
// Name will be inserted at beginning if where<0, at end if where>0.
// Note: subname may be NULL.
- (NXAtom)addToPath:WaisClass inPos:(int)where
    subdir:(const char *)subname ofFolder:(const char *)name
{
    char *buf;
    const char *home;
    NXAtom rtn;
    int i, len, cnt;
    id flist;
    
    // Valid folder?
    if(!name) return 0;
    if(!(name[0]=='/' || (name[0]=='~' && name[1]=='/'))) return 0;
    
    // Create buffer...
    home = NXHomeDirectory();
    len = 1 + strlen(name) + strlen("/");
    if(home) len += strlen(home);
    if(subname) len += strlen(subname);
    buf = s_malloc(len);
    if(!buf) return 0;
    
    // Replace initial '~' with home directory.
    if(name[0] == '~')
    {
    	if(home) strcpy(buf, home);
	strcat(buf, name+1);
    }
    else strcpy(buf, name);
    len = strlen(buf);
    
    // Append subfolder if any (fix trailing '/').
    if(subname)
    {
    	if(buf[len-1] != '/') strcat(buf, "/");
	strcat(buf, subname);
    }
    else if(buf[len-1] == '/') buf[len-1] = 0;

    // Append atomized version to WaisClass's path and return it.
    rtn = NXUniqueString(buf); s_free(buf);
    flist = [WaisClass folderList]; cnt = [flist count];
    for(i=0; i<cnt; i++) if(rtn == *((NXAtom *)[flist elementAt:i])) break;
    if(i >= cnt)
    {
    	if(where > 0) [flist addElement:(void *)&rtn];
	else [flist insert:(void *)&rtn at:0];
    }
    return rtn;
}

- createFolder:(const char *)name onError:(const char *)alertMsg
{
    char cmd[2*MAXPATHLEN];
    
    [Wais lockFileIO];
    if(!name)
    {
	if(stringTable) NXRunAlertPanel(
	    [stringTable valueForStringKey:"WAIStation"],
	    [stringTable valueForStringKey:alertMsg],
	    [stringTable valueForStringKey:"OK"], NULL, NULL);
	else { fprintf(stderr, "%s\n", alertMsg); fflush(stderr); }
	[Wais unlockFileIO];
	return nil;
    }
    if(0 == access(name, R_OK|W_OK|X_OK))
    	{ [Wais unlockFileIO]; return self; }
    sprintf(cmd, "mkdirs \'%s\'", name);
    system(cmd);
    if(0 == access(name, R_OK|W_OK|X_OK))
    	{ [Wais unlockFileIO]; return self; }
    if(stringTable) NXRunAlertPanel(
    	[stringTable valueForStringKey:"WAIStation"],
	[stringTable valueForStringKey:alertMsg],
	[stringTable valueForStringKey:"OK"], NULL, NULL);
    else { fprintf(stderr, "%s\n", alertMsg); fflush(stderr); }
    [Wais unlockFileIO];
    return nil;
}

- usePrefs
{
    const char *baseFolder, *thisFolder, *number;
    const char *docTypes, *typeStart, *typeExten, *typeEnd, *typesDone;
    char thisType[512], thisExten[512];
    NXAtom folder;
    unsigned int rank;
    int limit;
    
    // Set flag to open or not open docs as they are retrieved.
    if(!NXGetDefaultValue("WAIStation", "OpenOnRetrieval")
    	|| 'Y'==*NXGetDefaultValue("WAIStation", "OpenOnRetrieval")
    	|| 'y'==*NXGetDefaultValue("WAIStation", "OpenOnRetrieval"))
	isOpenOnRetrieval = YES;
    else isOpenOnRetrieval = NO;
    
    // Set class-wide search limit for questions.
    if((number=NXGetDefaultValue("WAIStation", "MaxSearchResults"))
    	&& 1==sscanf(number, " %d ", &limit))
	[WaisQuestion setSearchLimit:limit];
    
    // Use document type ranking and file extension info.
    if(docTypes = NXGetDefaultValue("WAIStation", "DocumentTypes"))
    	for(rank=1, typeStart=docTypes, typeExten=strchr(typeStart,'='),
		typesDone=docTypes+strlen(docTypes),
		typeEnd=(strchr(typeStart,',') ? 
		    strchr(typeStart,','):typesDone);
	    typeStart && typeStart<typesDone;
	    rank++, typeStart=typeEnd+(typeEnd[0]==',' ? 1:0),
	    	typeExten=strchr(typeStart,'='),
		typeEnd=(strchr(typeStart,',') ?
		    strchr(typeStart,','):typesDone))
	{
	    if(typeExten && typeExten<typeEnd)
	    {
		strncpy(thisType, typeStart, typeExten-typeStart);
		thisType[typeExten-typeStart] = 0;
		strncpy(thisExten, typeExten+1, typeEnd-typeExten-1);
		thisExten[typeEnd-typeExten-1] = 0;
		[WaisDocument registerExtension:thisExten forType:thisType];
	    }
	    else
	    {
		strncpy(thisType, typeStart, typeEnd-typeStart);
		thisType[typeEnd-typeStart] = 0;
	    }
	    [WaisQuestion preferDocumentType:thisType withRank:rank];
	}
    
    // Create user directories if necessary and prepend to appropriate paths.
    if(!(baseFolder = NXGetDefaultValue("WAIStation", "UserFolder")))
	baseFolder = "~/Library/WAIS";

    if(thisFolder = NXGetDefaultValue("WAIStation", "UserSourcesFolder"))
	folder = [self addToPath:[WaisSource class] inPos:(-1)
	    subdir:NULL ofFolder:thisFolder];
    else folder = [self addToPath:[WaisSource class] inPos:(-1)
    	subdir:"sources" ofFolder:baseFolder];
    [self createFolder:folder
    	onError:"Can't create folder for WAIS sources"];
    
    if(thisFolder = NXGetDefaultValue("WAIStation", "UserDocumentsFolder"))
	folder = [self addToPath:[WaisDocument class] inPos:(-1)
	    subdir:NULL ofFolder:thisFolder];
    else folder = [self addToPath:[WaisDocument class] inPos:(-1)
    	subdir:"documents" ofFolder:baseFolder];
    [self createFolder:folder
    	onError:"Can't create folder for WAIS documents"];
    
    if(thisFolder = NXGetDefaultValue("WAIStation", "UserQuestionsFolder"))
	folder = [self addToPath:[WaisQuestion class] inPos:(-1)
	    subdir:NULL ofFolder:thisFolder];
    else folder = [self addToPath:[WaisQuestion class] inPos:(-1)
    	subdir:"questions" ofFolder:baseFolder];
    [self createFolder:folder
    	onError:"Can't create folder for WAIS questions"];
    
    // Append system directories (if any) to appropriate paths.
    if(thisFolder = NXGetDefaultValue("WAIStation", "SystemSourcesFolder"))
	[self addToPath:[WaisSource class] inPos:1
	    subdir:NULL ofFolder:thisFolder];
    else if(baseFolder = NXGetDefaultValue("WAIStation", "SystemFolder"))
	[self addToPath:[WaisSource class] inPos:1
	    subdir:"sources" ofFolder:baseFolder];
    return self;
}

- updatePrefs:sender
{
    char number[128];
    
    // Get new prefs from panel and save in database.
    if([prefsIsOpenOnRetrieval state] != 0)
    	NXWriteDefault("WAIStation", "OpenOnRetrieval", "YES");
    else NXWriteDefault("WAIStation", "OpenOnRetrieval", "NO");
    sprintf(number, "%d", [prefsSearchLimit intValue]);
    NXWriteDefault("WAIStation", "MaxSearchResults", number);
    if([prefsDocumentTypes stringValue])
    	NXWriteDefault("WAIStation",
	    "DocumentTypes", [prefsDocumentTypes stringValue]);
    if([prefsSystemFolder stringValue])
    	NXWriteDefault("WAIStation",
	    "SystemFolder", [prefsSystemFolder stringValue]);
    if([prefsSystemSourcesFolder stringValue])
    	NXWriteDefault("WAIStation",
	    "SystemSourcesFolder", [prefsSystemSourcesFolder stringValue]);
    if([prefsUserSourcesFolder stringValue])
    	NXWriteDefault("WAIStation",
	    "UserSourcesFolder", [prefsUserSourcesFolder stringValue]);
    if([prefsUserDocumentsFolder stringValue])
    	NXWriteDefault("WAIStation",
	    "UserDocumentsFolder", [prefsUserDocumentsFolder stringValue]);
    if([prefsUserQuestionsFolder stringValue])
    	NXWriteDefault("WAIStation",
	    "UserQuestionsFolder", [prefsUserQuestionsFolder stringValue]);
    [self usePrefs];
    return self;
}

- prefs:sender
{
    const char *number;
    int limit;
    
    if(!prefsPanel) [NXApp loadNibSection:"Preferences.nib" owner:self];
    if(!NXGetDefaultValue("WAIStation", "OpenOnRetrieval")
    	|| 'Y'==*NXGetDefaultValue("WAIStation", "OpenOnRetrieval")
    	|| 'y'==*NXGetDefaultValue("WAIStation", "OpenOnRetrieval"))
	[prefsIsOpenOnRetrieval setState:1];
    else [prefsIsOpenOnRetrieval setState:0];
    
    if((number=NXGetDefaultValue("WAIStation", "MaxSearchResults"))
    	&& 1==sscanf(number, " %d ", &limit))
	[prefsSearchLimit setIntValue:limit];
    [prefsSearchLimit sendAction:[prefsSearchLimit action]
    	to:[prefsSearchLimit target]];
    
    [prefsDocumentTypes
    	setStringValue:NXGetDefaultValue("WAIStation", "DocumentTypes")];

    [prefsSystemFolder
    	setStringValue:NXGetDefaultValue("WAIStation", "SystemFolder")];
    [prefsSystemSourcesFolder
    	setStringValue:NXGetDefaultValue("WAIStation", "SystemSourcesFolder")];
    [prefsUserSourcesFolder
    	setStringValue:NXGetDefaultValue("WAIStation", "UserSourcesFolder")];
    [prefsUserDocumentsFolder
    	setStringValue:NXGetDefaultValue("WAIStation", "UserDocumentsFolder")];
    [prefsUserQuestionsFolder
    	setStringValue:NXGetDefaultValue("WAIStation", "UserQuestionsFolder")];

    [prefsPanel update];
    [prefsPanel makeKeyAndOrderFront:self];
    return self;
}

+ (const char *)defaultFolder
{
    if(NXGetDefaultValue("WAIStation", "UserFolder"))
	return NXGetDefaultValue("WAIStation", "UserFolder");
    else if(NXGetDefaultValue("WAIStation", "UserSourcesFolder"))
	return NXGetDefaultValue("WAIStation", "UserSourcesFolder");
    else return [[Wais folderList] elementAt:0];
}

// ----------------------- RETRIEVAL ------------------------ //


// Callback from retrieval thread to main thread.
- retrievalDone:data
{
    int docAt, ok;
    id doc, check_rtn;

    if(!data || ![data isKindOf:[List class]]
    	|| ![[data objectAt:0] isKindOf:[Wais class]])
	return nil;
    doc = [data objectAt:0];
    check_rtn = [data objectAt:1];
    [data free];
    
    // If successfully retrieved and user desires docs to be opened on
    // retrieval, pop open doc.
    if(check_rtn && isOpenOnRetrieval) [self openFile:[doc key] ok:&ok];

    // Enable browser entry for doc if check_rtn non-nil, else remove it.
    if(documentPaletteBrowser
	&& (docAt=[documentPaletteBrowser indexOfEntry:[doc key]])>=0)
    {
	if(check_rtn) [documentPaletteBrowser setEntryEnabled:YES at:docAt];
	else [documentPaletteBrowser removeEntryAt:docAt];
    }
    else if(sourcePaletteBrowser
	&& (docAt=[sourcePaletteBrowser indexOfEntry:[doc key]])>=0)
    {
	if(check_rtn) [sourcePaletteBrowser setEntryEnabled:YES at:docAt];
	else [sourcePaletteBrowser removeEntryAt:docAt];
    }
    return self;
}

any_t retrieval_thread(any_t rawArgs)
{
    retrieval_args args = (retrieval_args)rawArgs;
    id doc, src, check_rtn, data;
    
    while(YES)
    {
	// Wait for retrieval requests to show up in list.
	mutex_lock(args->requestMutex);
	while([args->docList count] <= 0)
	    condition_wait(args->requestCondition, args->requestMutex);
	doc = [args->docList objectAt:0];
	mutex_unlock(args->requestMutex);
	if(!doc) continue;

	// Only one retrieval at a time, since same port may be used.
	// If doc file describes a source, load it into Wais system.
	// Else write auxiliary WAIS file to preserve access info.
	check_rtn = [doc retrieve];
	if(check_rtn)
	{
	    if([doc valueForStringKey:":type"]
	    	&& 0==strcmp([doc valueForStringKey:":type"], "WSRC"))
	    {
	    	src = [[WaisSource alloc] initKey:[doc key]];
		[src readWaisFile];
	    }
	    else [doc writeWaisFile];
	}

	// Report back:
	// 1. Pop open retrieved doc if necessary.
	// 2. Enable browser entry if successful, else remove it.
	// (Use callback to main thread since AppKit is not thread-proof.
	//    Callback will free data list.)
	// (Note: [NXApp delegate] = us.)
	data = [[List alloc] init];
	[data addObject:doc]; [data addObject:check_rtn];
	[Wais callback:[NXApp delegate]
	    perform:@selector(retrievalDone:) with:data];

	// Remove entry from request list.
	mutex_lock(args->requestMutex);
	[args->docList removeObjectAt:0];
	mutex_unlock(args->requestMutex);
    }
    // Never reaches this.
    return 0;
}

- restartThread
{
    // Already running?
    if(retrievalThread) return nil;
    
    // Create and detach thread.
    retrievalArgs.docList = retrievalList;
    retrievalArgs.requestMutex = requestMutex = mutex_alloc();
    retrievalArgs.requestCondition = requestCondition = condition_alloc();
    retrievalThread = cthread_fork(retrieval_thread, (any_t)&retrievalArgs);
    if(!retrievalThread) return nil;
    cthread_detach(retrievalThread);
    return self;
}

- terminateThread
{
    int i;
    
    // Try to abort the retrieval thread.
    //!!! Zapping threads may screw up the mutex locking, so we don't
    //!!! try to recycle old mutexes and conditions.  Even if old thread stays
    //!!! around, it's probably not doing much, so its freedom from the
    //!!! new mutexes shouldn't be a problem.
    if(!retrievalThread) return self;
    thread_suspend(cthread_thread(retrievalThread));
    cthread_abort(retrievalThread);
    retrievalThread = 0;
    requestMutex = 0;
    requestCondition = 0;
    [Wais waisNewLocks];
    
    // Clear retrieval request list.
    // Update palettes (removing disabled entries).
    [retrievalList empty];
    for(i=[documentPaletteBrowser count]-1; i>=0; i--)
    	if(![documentPaletteBrowser isEntryEnabledAt:i])
	    [documentPaletteBrowser removeEntryAt:i];
    [documentPaletteBrowser update];
    for(i=[sourcePaletteBrowser count]-1; i>=0; i--)
    	if(![sourcePaletteBrowser isEntryEnabledAt:i])
	    [sourcePaletteBrowser removeEntryAt:i];
    [sourcePaletteBrowser update];
    return self;
}

- cancelRetrievals:sender
{
    [self terminateThread];
    [self restartThread];
    return self;
}

//!!! This method should be called before doing any operation that might
//!!! access a WaisDocument.  If it is being retrieved, you should
//!!! not access it as that might collide with operations in the
//!!! retrieval thread.
- (BOOL)isDocumentBeingRetrieved:waisDoc
{
    int docAt;

    if(requestMutex) mutex_lock(requestMutex);
    docAt = [retrievalList indexOf:waisDoc];
    if(requestMutex) mutex_unlock(requestMutex);
    if(docAt!=NX_NOT_IN_LIST && docAt>=0) return YES;
    return NO;
}

- retrieveDocuments:(const char *)keyList
{
    int docAt;
    char *docKeys, *thisKey, *nextKey;
    id doc, inBrowser;
    BOOL isSource;
    
    // Assumes keyList is TAB-separated list of keys.
    if(!keyList || !retrievalThread) return nil;
    if(!(docKeys = s_malloc(strlen(keyList) + 1))) return nil;
    strcpy(docKeys, keyList);
    for(thisKey=docKeys, nextKey=strchr(thisKey, '\t');
    	thisKey; thisKey=nextKey, nextKey=strchr(thisKey?thisKey:"", '\t'))
    {
    	if(nextKey) *nextKey++ = 0;
	
	// Check if doc is aleady retrieved or being retrieved (then ignore).
	if(!(doc = [WaisDocument objectForKey:thisKey])) continue;
	if([self isDocumentBeingRetrieved:doc] || [doc isRetrieved])
	    continue;

	// Check if it is really a source.
	isSource = NO;
	inBrowser = documentPaletteBrowser;
	if([doc valueForStringKey:":type"]
	    && 0==strcmp([doc valueForStringKey:":type"], "WSRC"))
		isSource = YES;
	if(isSource) inBrowser = sourcePaletteBrowser;
	
	// Enter into correct palette browser, but disabled.
	if(inBrowser)
	{
	    docAt = [inBrowser indexAddEntry:[doc key]];
	    [inBrowser setEntryEnabled:NO at:docAt];
	    [inBrowser update];
	}
	
	// Enter doc into queue and notify thead of retrieval request.
	if(requestMutex) mutex_lock(requestMutex);
	[retrievalList addObjectIfAbsent:doc];
	if(requestMutex) mutex_unlock(requestMutex);
	if(requestCondition) condition_signal(requestCondition);
    }
    s_free(docKeys);
    return self;
}

- retrieveDocumentsFrom:sender
{
    return [self retrieveDocuments:[sender stringValue]];
}


// ----------------------- PANEL SETUP ----------------------- //


- init
{
    id Handlers;
    
    [super init];

    // Classify file types.
    Handlers = [[List alloc] initCount:0];
    [Handlers addObject:[QuestionDoc class]];
    [Handlers addObject:[SourceDoc class]];
    [super setDocHandlers:Handlers];
    launchWithCreateDoc = YES;

    // Keep track of retrieval threads.
    retrievalList = [[List alloc] initCount:0];
    retrievalThread = 0;
    return self;
}

- free
{
    if(retrievalThread) [self terminateThread];
    [retrievalList free];
    if(requestMutex) mutex_free(requestMutex);
    if(requestCondition) condition_free(requestCondition);
    return [super free];
}

- appDidInit:sender
{
    char userInfo[1024];
    char hostname[512];

    [super appDidInit:sender];
    
    // Enable Alert Panels for Wais classes.
    [Wais setStringTable:stringTable];
    
    // Set up according to preferences.
    [self usePrefs];
    
    // Set user registration string.
    gethostname(hostname, 512); hostname[512-1] = 0;
    sprintf(userInfo, "WAIStation.app %s, from host: %s, user: %s",
	WAISTATION_VERSION, hostname, WAIS_USER);
    [WaisSource registerUser:userInfo];
	
    // Pop up source and doc palettes.
    [self sourcePalette:self];
    [self documentPalette:self];
    [sourcePalettePanel makeKeyAndOrderFront:self];
    
    // Start retrieval thread.
    // Must be done after doc and source palette created if we want updates!
    [self restartThread];
    return self;
}

- help:sender
{
    if(!helpPanel) [NXApp loadNibSection:"Help.nib" owner:self];
    [helpPanel makeKeyAndOrderFront:self];
    return self;
}

- info:sender
{
    if(!infoPanel) [NXApp loadNibSection:"Info.nib" owner:self];
    [infoPanel makeKeyAndOrderFront:self];
    return self;
}

- bugReport:sender
{
    //!!! Note the "MailSendDemo" port is undocumented.
    id mailSpeaker = [[MailSpeaker alloc] init]; 
    port_t mailPort = NXPortFromName("MailSendDemo", NULL); 

    if(!mailSpeaker || mailPort==PORT_NULL)
    	{ [mailSpeaker free]; return nil; }
    [mailSpeaker setSendPort:mailPort];
    [mailSpeaker openSend];
    if(NXGetDefaultValue("WAIStation", "BugReportAddress"))
	[mailSpeaker setTo:NXGetDefaultValue("WAIStation", 
	    "BugReportAddress")];
    if(NXGetDefaultValue("WAIStation", "BugReportCC"))
	[mailSpeaker setCc:NXGetDefaultValue("WAIStation", "BugReportCC")];
    if(NXGetDefaultValue("WAIStation", "BugReportSubject"))
	[mailSpeaker setSubject:NXGetDefaultValue("WAIStation", 
	    "BugReportSubject")];
    if(NXGetDefaultValue("WAIStation", "BugReportMessage"))
	[mailSpeaker setBody:NXGetDefaultValue("WAIStation", 
	    "BugReportMessage")];
    [mailSpeaker free];
    port_deallocate(task_self(), mailPort); 
    return self;
}

- signMeUp:sender
{
    //!!! Note the "MailSendDemo" port is undocumented.
    id mailSpeaker = [[MailSpeaker alloc] init]; 
    port_t mailPort = NXPortFromName("MailSendDemo", NULL); 

    if(!mailSpeaker || mailPort==PORT_NULL)
    	{ [mailSpeaker free]; return nil; }
    [mailSpeaker setSendPort:mailPort]; 
    [mailSpeaker openSend];
    if(NXGetDefaultValue("WAIStation", "MailListAddress"))
	[mailSpeaker setTo:NXGetDefaultValue("WAIStation", "MailListAddress")];
    [mailSpeaker setSubject:"SUBSCRIBE"];
    if(NXGetDefaultValue("WAIStation", "MailListMessage"))
	[mailSpeaker setBody:NXGetDefaultValue("WAIStation", 
	    "MailListMessage")];
    [mailSpeaker free];
    port_deallocate(task_self(), mailPort); 
    return self;
}

- sourcePalette:sender
{
    int i, j, n, m;
    BOOL quiet;
    id srcList, srcPath;
    const char **srcFolder;
    
    if(!(srcPath = [WaisSource folderList])) return nil;
    if(!sourcePalettePanel)
    {
    	[NXApp loadNibSection:"SourcePalette.nib" owner:self];
	[[sourcePaletteBrowser setAlphabetized:YES] setAbbreviated:YES];
	[sourcePaletteBrowser setEditable:YES];
    }
    
    // Load all directories in source path (ignore missing ones).
    [sourcePaletteBrowser clear];
    quiet = [Wais isQuiet];
    [Wais setQuiet:YES];
    m = [srcPath count];
    for(j=0; j<m; j++) if((srcFolder=(const char **)[srcPath elementAt:j])
    	&& (*srcFolder) && (srcList=[WaisSource loadFolder:*srcFolder]))
    {
	n = [srcList count];
	for(i=0; i<n; i++)
	    [sourcePaletteBrowser addEntry:[[srcList objectAt:i] key]];
	[srcList free];
    }
    [Wais setQuiet:quiet];
    [sourcePaletteBrowser update];
    [sourcePalettePanel orderFront:self];
    
    if(!sourcePaletteIWC) sourcePaletteIWC =
    	[[IconWellControl alloc] initWindow:sourcePalettePanel];
    return self;
}

- documentPalette:sender
{
    int i, j, n, m;
    BOOL quiet;
    id docList, docPath;
    const char **docFolder;
    
    if(!(docPath = [WaisDocument folderList])) return nil;
    if(!documentPalettePanel)
    {
    	[NXApp loadNibSection:"DocumentPalette.nib" owner:self];
	[[documentPaletteBrowser setAlphabetized:YES] setAbbreviated:YES];
	[documentPaletteBrowser setEditable:YES];
    }
    
    // Load all directories in document path (ignore missing ones).
    [documentPaletteBrowser clear];
    quiet = [Wais isQuiet];
    [Wais setQuiet:YES];
    m = [docPath count];
    for(j=0; j<m; j++) if((docFolder=(const char **)[docPath elementAt:j])
    	&& (*docFolder) && (docList=[WaisDocument loadFolder:*docFolder]))
    {	    
	n = [docList count];
	for(i=0; i<n; i++)
	{
	    if([self isDocumentBeingRetrieved:[docList objectAt:i]])
	    	continue;
	    else if([[docList objectAt:i] isRetrieved])
		[documentPaletteBrowser addEntry:[[docList objectAt:i] key]];
	}
	[docList free];
    }
    [Wais setQuiet:quiet];
    [documentPaletteBrowser update];
    [documentPalettePanel orderFront:self];

    if(!documentPaletteIWC) documentPaletteIWC =
    	[[IconWellControl alloc] initWindow:documentPalettePanel];
    return self;
}


// ----------------------- DELEGATED FILE OPS ------------------------ //


- (int)openFile:(const char *)fileName ok:(int *)flag
{
    int rtn;
    
    // Try to open in this application first, then in Workspace.
    if([super handlerForFile:fileName])
    {
	if([super openForHandlerAt:(-1) name:fileName]) *flag = YES;
	else *flag = NO;
	rtn = 0;
    }
    else
    {
	[Wais lockTransaction];
	[[NXApp appSpeaker] setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
	rtn = [[NXApp appSpeaker] openFile:fileName ok:flag];
	[Wais unlockTransaction];
    }
    return rtn;
}

- (int)removeFile:(const char *)fileName ok:(int *)flag
{
    int rtn = 0;
    char *auxFile = 0;
    id doc = nil;
    
    // If a WaisDocument, set it as unretrieved
    // and delete the associated ".wais" file too.

    if(!fileName) return (-1);
    *flag = NO;
    if(doc = [WaisDocument objectForKey:fileName])
    {
	auxFile = s_malloc(1+strlen(fileName)+strlen(".wais"));
	if(!auxFile) return (-1);
	strcpy(auxFile, fileName); strcat(auxFile, ".wais");
	//!!! Potential thread conflict if doc is being retrieved.
	// (But shouldn't be a problem since such entries are disabled.)
	[doc setUnretrieved];
    }
    [Wais lockFileIO];
    if(doc) rtn = unlink(auxFile);
    if(rtn == 0) rtn = unlink(fileName);
    else unlink(fileName);
    [Wais unlockFileIO];
    if(auxFile) s_free(auxFile);
    if(rtn != 0) return rtn;
    *flag = YES;
    return 0;
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.