// WaisSource.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

*/
//

#import "WaisSource.h"

// Search path for sources.
static id sourceFolderList;

// Error panel title.
static char *errorTitle = "WAIS Source Error!";

// User information sent to remote servers.
static NXAtom userInfo;

// Decoders for WAIS structured files.
_WaisDecoder waisDataFileDecoder[] = 
{
    { ":data-file",	W_FIELD,0,0,	ReadString,3,	WriteString,2, 
    					MAX_SYMBOL_SIZE },
    { ":index-mode",	W_FIELD,0,0,	ReadSymbol,3,	WriteSymbol,2, 
    					MAX_SYMBOL_SIZE },
    { NULL }
};

_WaisDecoder waisSourceDecoder[] = 
{
    { ":version",	W_FIELD,0,0,	ReadLongS,2,	WriteLongS,2 },
    { ":ip-name",	W_FIELD,0,0,	ReadString,3,	WriteString,2, 
    					MAX_SYMBOL_SIZE },
    { ":ip-address",	W_FIELD,0,0,	ReadString,3,	WriteString,2, 
    					MAX_SYMBOL_SIZE },
    { ":configuration",	W_FIELD,0,0,	ReadString,3,	NULL,0,
    					MAX_SYMBOL_SIZE },
    { ":tcp-port",	W_FIELD,0,0,	ReadLongS,2,	WriteLongS,2 },
    { ":cost",		W_FIELD,0,0,	ReadDoubleS,2,	WriteDoubleS,2 },
    { ":cost-unit",	W_FIELD,0,0,	ReadSymbol,3,	WriteSymbol,2, 
    					MAX_SYMBOL_SIZE },
    { ":database-name",	W_FIELD,0,0,	ReadString,3,	WriteString,2, 
    					MAX_SYMBOL_SIZE },
    { ":description",	W_FIELD,0,0,	ReadString,3,	WriteString,2, 
    					READ_BUF_SIZE },
    { ":maintainer",	W_FIELD,0,0,	ReadString,3,	WriteString,2, 
    					MAX_SYMBOL_SIZE },
    { ":update-time",	W_FIELD,0,0,	ReadListX,1,	NULL,0 },//!!!
    { ":index-list",	W_LIST,
    	":include",	waisDataFileDecoder },
    { NULL }
};


@implementation WaisSource

+ folderList
{
    return sourceFolderList;
}

+ setFolderList:aList
{
    if(sourceFolderList) [sourceFolderList free];
    sourceFolderList = aList;
    return self;
}

+ (const char *)defaultHomeFolder
{
    return "/Library/WAIS/sources";
}

+ (const char *)fileStructName
{
    return ":source";
}

+ (WaisDecoder)fileStructDecoder
{
    return waisSourceDecoder;
}

+ (const char *)errorTitle
{
    return errorTitle;
}

+ registerUser:(const char *)userInfoString
{
    userInfo = NXUniqueString(userInfoString);
    return self;
}

+ (BOOL)checkFileName:(const char *)fileName
{
    if(!fileName) return NO;
    if(strlen(fileName) <= strlen(W_S_EXT)) return NO;
    if(!strstr(fileName, W_S_EXT)) return NO;
    if(0 != strcmp(W_S_EXT, strstr(fileName, W_S_EXT))) return NO;
    return YES;
}

- setKey:(const char *)aKey
{
    const char *tail;

    // Set ":filename" field to be consistent with new key.
    // This is so source can function as its own source-id.
    if(![super setKey:aKey]) return nil;
    if(![self key]) return nil;
    tail = strrchr([self key], '/');
    if(tail) tail++;
    else tail = [self key];
    [self insertStringKey:":filename" value:tail];
    return self;
}

- initKey:(const char *)aKey
{
    [super initKey:aKey];
    dataFileList = [[List alloc] init];

    // Redundant...
    isConnected = NO;
    connection = NULL;
    bufferLength = 0;
    listCounter = 0;
    return self;
}

- free
{
    // If no other source needs our connection, close it.
    [self setConnected:NO];
    [[dataFileList freeObjects] free];
    return [super free];
}

- dataFileList
{
    return dataFileList;
}

- addDataFile:data
{
    if(![data isKindOf:[WaisDataFile class]]) return nil;
    [dataFileList addObjectIfAbsent:data];
    return self;
}

- removeDataFile:data
{
    return [dataFileList removeObject:data];
}

- clearDataFiles
{
    //!!! We aren't sharing data files, so free them.
    [[dataFileList freeObjects] empty];
    return self;
}

- (FILE *)connection
{
    return connection;
}

- (BOOL)isConnected
{
   return isConnected;
}

- (long)bufferLength
{
    return bufferLength;
}

- copyConnection:(FILE *)theConnection length:(long)theBufferLength
{
    connection = theConnection;
    bufferLength = theBufferLength;
    return self;
}

- setConnected:(BOOL)yn;
{
    int i, n;
    id s;
    const char *server, *service, *otherServer, *otherService;
    char hostname[512];
    static char request[MAX_MESSAGE_LEN], response[MAX_MESSAGE_LEN];
    
    // Lock to prevent conflict with network transactions in other threads.
    [Wais lockTransaction];

    // When disconnecting, make sure no other source is using connection.
    if(!yn)
    {
	if(!isConnected)
	{
	    connection = NULL;
	    [Wais unlockTransaction];
	    return self;
	}
	n = [[WaisSource waisObjectList] count];
	for(i=0; i<n; i++)
	    if(self!=(s=[[WaisSource waisObjectList] objectAt:i])
	    	&& [s isKindOf:[WaisSource class]]
	    	&& connection==[s connection])
		    break;
	if(i >= n) close_connection(connection);
	connection = NULL;
	isConnected = NO;
	[Wais unlockTransaction];
	return self;
    }
    
    // Establish connection.  Don't connect to local host.
    if(isConnected) { [Wais unlockTransaction]; return self; }
    bufferLength = MAX_MESSAGE_LEN;
    gethostname(hostname, 512); hostname[512-1] = 0;
    server = [self valueForStringKey:":ip-name"];
    if(!server || strlen(server)==0)
    	server = [self valueForStringKey:":ip-address"];
    if(server && (strlen(server)==0
    	|| strcmp(server, hostname)==0 || strcmp(server, "localhost")==0))
    	server = 0;
    service = [self valueForStringKey:":tcp-port"];
    if(service && strlen(service)==0) service = 0;
    if(!server || !service) connection = NULL;
    else if(!(connection = connect_to_server(server, atoi(service))))
    {
	[Wais unlockTransaction];
    	ErrorMsg(errorTitle, "Can't connect to server %s.",
	    (server ? server : "localhost"));
	return nil;
    }

    // Send initialization message, giving info about user.
    if((bufferLength=init_connection(request, response,
    	MAX_MESSAGE_LEN, connection, (userInfo?userInfo:"anonymous"))) < 0)
    {
	[Wais unlockTransaction];
    	ErrorMsg(errorTitle, "Can't connect to server %s.",
	    (server ? server : "localhost"));
	connection = NULL; return nil;
    }
    isConnected = YES;

    // Let other sources with same host and port know they're ready.
    n = [[WaisSource waisObjectList] count];
    if(server && service) for(i=0; i<n; i++)
    {
    	s = [[WaisSource waisObjectList] objectAt:i];
	if(![s isKindOf:[WaisSource class]]) continue;
	if(!(otherServer=[s valueForStringKey:":ip-address"])
    	    && !(otherServer=[s valueForStringKey:":ip-name"]))
	    continue;
	if(!(otherService=[s valueForStringKey:":tcp-port"]))
	    continue;
	if(strcmp(otherServer, server)!=0
	    || strcmp(otherService, service)!=0)
	    continue;
	[s copyConnection:connection length:bufferLength];
    }
    [Wais unlockTransaction];
    return self;
}

- (short)readWaisStruct:(const char *)structName
    forElement:(const char *)elementName
    fromFile:(FILE *)file
    withDecoder:(WaisDecoder)theDecoder
{
    const char *config, *tail;
    char value[STRINGSIZE+MAX_SYMBOL_SIZE];
    short check_result;
    id data;
    
    // Decode data file entry into list.
    if(0 == strcmp(elementName, ":index-list"))
    {
	data = [[WaisDataFile alloc] initKey:NULL];
	check_result = [data readWaisStruct:structName
	    forElement:elementName fromFile:file withDecoder:theDecoder];
	if(check_result==FALSE || check_result==END_OF_STRUCT_OR_LIST)
	    { [data free]; return check_result; }
	[dataFileList addObject:data];
	return TRUE;
    }

    // Else do standard read.
    check_result = [super readWaisStruct:structName
	forElement:elementName fromFile:file withDecoder:theDecoder];

    // Hack ugly special case of ":configuration" field.
    if(0==strcmp(structName, ":source")
    	&& (config=[self valueForStringKey:":configuration"]))
    {
	read_subfield(config, "IPAddress", value, STRINGSIZE);
	[self insertStringKey:":ip-address" value:value];
	read_subfield(config, "RemotePort", value, STRINGSIZE);
	[self insertStringKey:":tcp-port" value:value];
	[self insertStringKey:":configuration" value:NULL];
    }

    // Match key w/ info, if full src record has been read.
    if(0 == strcmp(structName, [WaisSource fileStructName]))
    {
    	if(!key) [self setKey:[self valueForStringKey:":filename"]];
	else if(![self valueForStringKey:":filename"])
	{
	    tail = strrchr(key, '/');
	    if(tail) tail++;
	    else tail = key;
	    [self insertStringKey:":filename" value:tail];
	}
    }	
    return check_result;
}

- readWaisFile
{
    [self setConnected:NO];
    if(![super readWaisFile]) return nil;
    return self;
}

- (short)writeWaisStruct:(const char *)structName
    forElement:(const char *)elementName
    toFile:(FILE *)file
    withDecoder:(WaisDecoder)theDecoder
{
    id data;
    short check_result;
    
    // Check if need next subobject in a list to extract data from.
    // The listCounter keeps track of where we are in list.
    if(0 == strcmp(elementName, ":index-list"))
    {
    	data = [dataFileList objectAt:listCounter];
	if(!data) { listCounter = 0; return END_OF_STRUCT_OR_LIST; }
	else listCounter++;
	check_result = [data writeWaisStruct:structName
	    forElement:elementName toFile:file withDecoder:theDecoder];
	if(check_result==FALSE || check_result==END_OF_STRUCT_OR_LIST)
	    listCounter = 0;
	return check_result;
    }
    
    // Else do normal write (clearing list counter).
    listCounter = 0;
    return [super writeWaisStruct:structName
	forElement:elementName toFile:file withDecoder:theDecoder];
}

- writeWaisFile
{
    char buf[1024];
    
    // Fill in any missing essential fields with default values first.
    if(![self valueForStringKey:":cost"]
    	|| strlen([self valueForStringKey:":cost"])==0)
	[self insertStringKey:":cost" value:"0.00"];
    if(![self valueForStringKey:":cost-unit"]
    	|| strlen([self valueForStringKey:":cost-unit"])==0)
	[self insertStringKey:":cost-unit" value:":free"];
    if(![self valueForStringKey:":description"]
	|| strlen([self valueForStringKey:":description"])==0)
    {
	sprintf(buf, "Created with Objective-C WAIS by %s on %s.",
	    current_user_name(), printable_time());
	[self insertStringKey:":description" value:buf];
    }
    return [super writeWaisFile];
}

@end


@implementation WaisDataFile

+ (const char *)fileStructName
{
    return ":include";
}

+ (WaisDecoder)fileStructDecoder
{
    return waisDataFileDecoder;
}

@end
