/* util.c
   Utility routines to maintain and process gopher data structures
   and events. */

     /*---------------------------------------------------------------*/
     /* Xgopher        version 1.2     20 November 1991               */
     /*                version 1.1     20 April 1991                  */
     /*                version 1.0     04 March 1991                  */
     /* X window system client for the University of Minnesota        */
     /*                                Internet Gopher System.        */
     /* Allan Tuchman, University of Illinois at Urbana-Champaign     */
     /*                Computing and Communications Services Office   */
     /* Copyright 1992 by                                             */
     /*           the Board of Trustees of the University of Illinois */
     /* Permission is granted to freely copy and redistribute this    */
     /* software with the copyright notice intact.                    */
     /*---------------------------------------------------------------*/


#include <stdio.h>

#include "conf.h"
#include "gopher.h"
#include "globals.h"
#include "gui.h"
#include "item.h"
#include "itemList.h"
#include "dir.h"
#include "dirList.h"
#include "util.h"
#include "ioutil.h"
#include "misc.h"
#include "net.h"
#include "appres.h"
#include "prefixP.h"

#include <string.h>
#if defined(sun) || defined(sgi)
#include <strings.h>
#endif

#include <sys/file.h>

extern errno;


/* makeItem
   Given the contents, allocate and build a new gopher item */

gopherItemP
makeItem(t, titleString, selectString, host, port, plus)
char	t;
char	*titleString;
char	*selectString;
char	*host;
int	port;
BOOLEAN	plus;
{
	gopherItemP	gi = newItem();

	gi->type = t;
	strncpy(USER_STRING(gi), titleString, USER_STRING_LEN);
	vStringSet(&(gi->selector), selectString);
	strncpy(gi->host, host, HOST_STRING_LEN);
	gi->port = port;
	gi->plus = FALSE;

	insertPrefix(gi);

	return gi;
}


/* insertPrefix
   Insert a textual prefix in the displayable name to identify the item type */

void
insertPrefix(gi)
gopherItemP	gi;
{
	switch (gi->type) {
	    case A_FILE:
		PREFIX(gi, prefixFile);
		break;
	    case A_DIRECTORY:
		PREFIX(gi, prefixDir);
		break;
	    case A_CSO:
		PREFIX(gi, prefixCSO);
		break;
	    case A_INDEX:
		PREFIX(gi, prefixIndex);
		break;
	    case A_TELNET:
		PREFIX(gi, prefixTelnet);
		break;
	    case A_TN3270:
		PREFIX(gi, prefixTn3270);
		break;
	    case A_IMAGE:
		PREFIX(gi, prefixImage);
		break;
	    case A_SOUND:
		PREFIX(gi, prefixSound);
		break;
	    case A_BINARY:
		PREFIX(gi, prefixBinary);
		break;
	    default:
		PREFIX(gi, prefixUnknown);
	}
}



/* getGopherItem
   receive a response from a gopher server, parsing it as it comes in.
   Return the item pointer for success, NULL for failure. */

gopherItemP
getGopherItem(gfd)
int	gfd;
{
	char			buffer[255];
	char	gType,
		gName[USER_STRING_LEN],
		gPath[SELECTOR_STRING_MAX_LEN],
		gHost[HOST_STRING_LEN];
	int	gPort;
	BOOLEAN	gPlus;


	if (readField(gfd, buffer, 255) <= 0) {
		/* immediate EOF or error -- no item read */
		return NULL;
	}

	/** Get the kind of file from the first character **/
	/** Set the text prefix of the item type **/

	gType = buffer[0];

	if (gType == A_EOI) return NULL;

	/* get the User Displayable name */

	strncpy(gName, buffer + 1, USER_STRING_LEN);

	/* terminate the string just in case it was too long */

	gName[USER_STRING_LEN - 1] = '\0';

	/* get the Pathname */

	if (readField(gfd, gPath, SELECTOR_STRING_MAX_LEN) <= 0) {
		/* EOF or error after TYPE/NAME  --
		   no complete item read */
		return NULL;
	}

	/* get the hostname */

	if (readField(gfd, gHost, HOST_STRING_LEN) == 0) {
		/* EOF or error after TYPE/NAME/PATH  --
		   no complete item read */
		return NULL;
	}

	/* get the port number */

	if (readLine(gfd, buffer, 255)<=0) {
		/* EOF or error after TYPE/NAME/PATH  --
		   Problem - should be a port number here.  But this
		   isn't as serious as above.  We'll try to proceed. */
		;
	}

	gPort = 0;

	/* Get the port number */

	gPort = atoi(buffer);

	gPlus = False;

	return makeItem(gType, gName, gPath, gHost, gPort, gPlus);
}


/* getGopherDir
   receive a directory from a gopher server.
   Return the directory pointer for success, NULL for failure. */

BOOLEAN
getGopherDir(gfd, d)
int	gfd;
gopherDirP	d;
{
	gopherItemP	gi;
	char		buffer[512];
	int  j;

	while ( (gi = getGopherItem(gfd)) != NULL ) {
		if (appResources->allItems  ||
				gi->type == A_FILE  ||
				gi->type == A_DIRECTORY ||
				gi->type == A_CSO ||
				gi->type == A_INDEX ||
				gi->type == A_TELNET ||
				gi->type == A_TN3270 ||
				gi->type == A_BINARY ||
				gi->type == A_IMAGE ||
				gi->type == A_SOUND) {
			appendItem(&(d->contents), gi);
		} else {
			freeItem(gi);
		}
	}

	return  (itemListLength(&(d->contents)) != 0);
} 


/* getDirectory
   load a new gopher directory as indicated by the selected item */

BOOLEAN
getDirectory(gi)
gopherItemP	gi;
{
	int		s;
	gopherDirP	d;

	s = connectToSocket(gi->host, gi->port);
	if (s < 0) {
		/* could not make a network connection to receive directory */
		networkError(s, gi->host, gi->port);
		return FALSE;
	}

	writeString(s, vStringValue(&(gi->selector)));
	writeString(s, EOL_STRING);

	d = newDir();
	if (! getGopherDir(s, d)) {
		/* failure to load directory */
		freeDir(d);
		close(s);
		showError(
			"There seems to be no information in this directory\n");
		return FALSE;
	}

	setDirTime(d);

	close(s);

	d->selectorItem = copyItem(gi);
	
	pushCurrentDir(d);

	displayCurrent();

	return TRUE;
}


/* updateDirectory
   reload an existing gopher directory (possibly an index search result) */

void
updateDirectory(d)
gopherDirP	d;
{
	int		s;
	gopherItemP	gi = d->selectorItem;

	showStatus("reloading requested directory", GSTAT_WAIT);

	s = connectToSocket(gi->host, gi->port);
	if (s < 0) {
		/* could not make a network connection to receive directory */
		networkError(s, gi->host, gi->port);
		showStatus("select an item from the list", GSTAT_SELECT);
		return;
	}

	writeString(s, vStringValue(&(gi->selector)));
	writeString(s, EOL_STRING);

	if (! getGopherDir(s, d)) {
		/* failure to re-load the directory */
		showError(
			"There seems to be no information in this directory\n");
	}

	setDirTime(d);

	close(s);
	showStatus("select an item from the list", GSTAT_SELECT);

	return;
}


/* getIndexDirectory
   load a new gopher directory resulting from an index search */

BOOLEAN
getIndexDirectory(gi, string)
gopherItemP	gi;
char		*string;
{
	int		s;
	gopherDirP	d;

	s = connectToSocket(gi->host, gi->port);
	if (s < 0) {
		/* could not make a network connection to receive index */
		networkError(s, gi->host, gi->port);
		return FALSE;
	}

	writeString(s, vStringValue(&(gi->selector)));

	/* if string is NULL, then the search string is already encoded in
	   the selector */

	if (string != NULL) {
		writen(s, "\t", 1);
		writeString(s, string);
	}

	writeString(s, EOL_STRING);

	d = newDir();
	if (! getGopherDir(s, d)) {
		/* failure to load directory */
		freeDir(d);
		close(s);
		showError(
			"There are no files selected by this index search\n");
		return FALSE;
	}

	setDirTime(d);

	close(s);

	d->selectorItem = copyItem(gi);
	if (string != NULL) {
		char	indexPath[PATH_NAME_LEN + INDEX_WORD_LEN];

		strcpy(indexPath, vStringValue(&(gi->selector)));
		strcat(indexPath, "\t");
		strcat(indexPath, string);
		vStringSet(&(d->selectorItem->selector), indexPath);
	}

	if (string != NULL) {
#define			INDEX_LABEL " - Index words: "
		char	*title = USER_STRING(d->selectorItem);
		int	len=strlen(title);
		int	remaining = USER_STRING_LEN - len;
		int	labelLen = strlen(INDEX_LABEL);

		title += len;
		strncpy(title, INDEX_LABEL, remaining);
		remaining -= labelLen;
		title += labelLen;
		strncpy(title, string, remaining);
	}
	
	pushCurrentDir(d);

	displayCurrent();

	return TRUE;
}


/* getFile
   access the Ascii text file as required by the selected item */

BOOLEAN
getFile(gi, indexString)
gopherItemP	gi;
char		*indexString;
{
	char		errorString[MESSAGE_STRING_LEN];
	char		tempName[PATH_NAME_LEN];
	int		tempFD;
	int		s;
	
	s = connectToSocket(gi->host, gi->port);
	if (s < 0) {
		/* could not make a network connection to receive file */
		networkError(s, gi->host, gi->port);
		return FALSE;
	}

	writeString(s, vStringValue(&(gi->selector)));
	writeString(s, EOL_STRING);

	getTempFile(tempName);
	if ((tempFD = open(tempName, O_WRONLY | O_CREAT, 0777)) < 0) {
		sprintf(errorString,
			"Cannot open the temporary file %s", tempName);
		showError(errorString);
		fprintf (stderr, "%s\n", errorString);
		perror("getFile");
		return FALSE;
	}

	copyAscii(s, tempFD, -1);

	close(s);
	close(tempFD);

	showFile(USER_STRING(gi), tempName, indexString);

	return TRUE;

}

/* getTempFile
   generate a temperary file name and return a pointer to it.
   The caller should check for a NULL file pointer.
   (The Unix routines mktemp(), tmpnam(), or tempnam() could be
   used as the body of this routine.) */

void
getTempFile(tempName)
char	*tempName;
{
	static int	fileCount = 0;
	static int	thisPID = 0;

	if (thisPID == 0) thisPID = getpid();
	sprintf (tempName, "%s/gop%d.%d",
			tildePath(appResources->tempDirectory),
			thisPID, fileCount++);

	return;
}


/* copyItemToFile
   copy a gopher item directly to a file */

BOOLEAN
copyItemToFile(gi, copyTypeProc)
gopherItemP	gi;
void		(*copyTypeProc)();
{
	int		s;
	char		*lastName;
	
	s = connectToSocket(gi->host, gi->port);
	if (s < 0) {
		/* could not make a network connection to receive file */
		networkError(s, gi->host, gi->port);
		return FALSE;
	}

	writeString(s, vStringValue(&(gi->selector)));
	writeString(s, EOL_STRING);

	/* get the last component of the name.  If there is no '/' in
	   the path name, be suspicious and send NULL. */

	lastName = rindex(vStringValue(&(gi->selector)), '/');
	if (lastName != NULL) lastName++;
	saveRequest(NULL, "", s, -1, copyTypeProc, lastName) ;

	return TRUE;

}


/* getImage
   get an image file and display it.  Since image display may take a
   while, we just fork the process and let it occur asynchronously. */

BOOLEAN
getImage(gi)
gopherItemP	gi;
{
	char	errorString[MESSAGE_STRING_LEN];
	char	tempName[PATH_NAME_LEN];
	char	message[MESSAGE_STRING_LEN];
	int	tempFD;
	int	s;
	char	imageCmd[PATH_NAME_LEN];	
	char	*path, *suffix=NULL;

#if    	!defined(SYSV) && !defined(__convex__) && !defined(__bsdi__)
	int     imagePID = 0;
#else
	pid_t   imagePID = 0;
#endif                          /* !defined(SYSV) */

	if (! appResources->allowImage ) {
		showError("Image display is not enabled for this session");
		return FALSE;
	}

	if (strlen ( appResources->imageCommand ) <= 0 ) {
		showError("Cannot execute the image command");
		return FALSE;
	}

	getTempFile(tempName);

	/* find the file name suffix which may indicate the image
	   file type -- if possible */

	path = vStringValue(&(gi->selector));
	suffix = rindex(path, '.');
	if (suffix != NULL) {

		/* find length of ".xxx".  If it's 4 or fewer characters
		   after the dot, assume that it's a meaningful suffix
		   and copy it to the end of the temp file name. */

		int	nch = strlen(suffix);

		if (nch <= 4) {
			strcat (tempName, suffix);
		}
	}

	strcpy(imageCmd, appResources->imageCommand);
	strcat(imageCmd, " ");
	strcat(imageCmd, tempName);
	
	s = connectToSocket(gi->host, gi->port);
	if (s < 0) {
		/* could not make a network connection to receive file */
		networkError(s, gi->host, gi->port);
		return FALSE;
	}

	writeString(s, vStringValue(&(gi->selector)));
	writeString(s, EOL_STRING);

	if ((tempFD = open(tempName, O_WRONLY | O_CREAT, 0700)) < 0) {
		sprintf(errorString,
			"Cannot open the temporary file %s", tempName);
		showError(errorString);
		fprintf (stderr, "%s\n", errorString);
		perror("getImage");
		return FALSE;
	}

	copyBin(s, tempFD, -1);

	close(s);
	close(tempFD);

	if ((imagePID = fork()) < 0) {

		/* we should immediately do a 
		      close (ConnectionNumber(XtDisplay(widget)));
		   here.
		*/
		sprintf(message,
		    "Unable to prepare for an image display (error %d)\n",
		    errno);
		showError(message);
		unlink (tempName);
		return FALSE;

	} else if (imagePID == 0) {
		system(imageCmd);
		unlink (tempName);

		exit(0);
	}

	/* parent just returns as child does the display */

	return TRUE;
}


#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/signal.h>

#if    	!defined(SYSV) && !defined(__convex__) && !defined(__bsdi__)
	static int     soundPID = 0;
#else
	static pid_t   soundPID = 0;
#endif                          /* !defined(SYSV) */

/* getSound
   get a sound file and play it.  Since sound will not conflict with
   visuals, this can be done asynchronously as a new process.

   X is run in a distributed environment.  As such, we take some pains
   to ensure that the requestor of the sound is the one who will actually
   hear the sound.  To wit, we strive to determine that the Xgopher
   client host and X display host are one and the same.  

   Note that this does not eliminate mischief, as the sound command
   specified in the resources can still be an "rsh" type of command. */

BOOLEAN
getSound(gi)
gopherItemP	gi;
{
	static BOOLEAN	firstTime = TRUE;
	static BOOLEAN	soundCommandOK = TRUE;
	static char	*soundCmd;
	char		message[MESSAGE_STRING_LEN];
	FILE		*soundCmdFile;
	int		s;

#if    	!defined(SYSV) && !defined(__convex__) && !defined(__bsdi__)
	static int     soundPID = 0;
#else
	static pid_t   soundPID = 0;
#endif                          /* !defined(SYSV) */

#if	 defined(sequent)

	/* this is supposed to be defined in <stdio.h>, but
	   it turns up missing on some machines - like the sequent */
	FILE		*popen();
#endif

	/* see if sound command is turned on */

	if (! appResources->hasSound) {
		showError("This machine does not support sounds.");
		return FALSE;
	}

	/* see if sound command is an executable file */

	if (firstTime) {
		firstTime = FALSE;
		soundCmd = cmdPath(appResources->soundCommand);
		if (soundCmd == NULL) {
			soundCommandOK = FALSE;
		}

		/* check here for X display (later) */
	}

	if (! soundCommandOK) {
		sprintf (message,
			"Unable to execute the sound play command: \'%s\'\n",
			soundCmd);
		showError(message);
		return FALSE;
				
	}

	if (soundPID != 0) {	/* sound may still be active */

#if     	!defined(SYSV) && !defined(__convex__) && !defined(__bsdi__) && !defined(linux) || defined(sgi)
		union wait	status;
		int		pid;
#else
		int		status;
		pid_t		pid;
#endif                          /* !defined(SYSV) */

		struct rusage	rusage;

		pid = wait3(&status, WNOHANG, &rusage);
		if (pid == 0) {
		    showError(
		    "Another sound file is still executing.  Try again later."
		        );
		    return FALSE;
		} else if (pid == soundPID) {
			soundPID = 0;
		} else {
			showError(
			    "It doesn't look good for playing any more sounds."
			    );
			return FALSE;
		}
	}

	if ((soundPID = fork()) < 0) {

		/* we should immediately do a 
		      close (ConnectionNumber(XtDisplay(widget)));
		   here.
		*/
		soundCommandOK = FALSE;
		sprintf(message,
		    "Unable to start playing the sound (error %d)\n",
		    errno);
		showError(message);
		return FALSE;

	} else if (soundPID != 0) {
		return TRUE;

	} else {

		s = connectToSocket(gi->host, gi->port);
		if (s < 0) {
			networkError(s, gi->host, gi->port);
			return FALSE;
		}

		writeString(s, vStringValue(&(gi->selector)));
		writeString(s, EOL_STRING);

		soundCmdFile = popen(soundCmd, "w");

		if (soundCmdFile == NULL) {
		    sprintf(message,
			"Unable to start the sound command (\'%s\')\n",
			    soundCmd);
			showError(message);
			close (s);
			exit(-1);
		}

		copyNetUntilEOF(s, soundCmdFile);

		close(s);
		pclose(soundCmdFile);
		exit(0);
	}
}


/* killSound
   kill a sound command process if possible. */

void
killSound()
{
	if (soundPID != 0) {	/* sound is or was active */

#if     	!defined(SYSV) && !defined(__convex__) && !defined(__bsdi__) && !defined(linux) || defined(sgi)
		union wait	status;
		int		pid;
#else
		int		status;
		pid_t		pid;
#endif                          /* !defined(SYSV) */

		struct rusage	rusage;

		pid = wait3(&status, WNOHANG, &rusage);
		if (pid == 0) {
			kill(soundPID, SIGTERM);
		}
		soundPID = 0;
	}
}


#define NET_BUFFER_SIZE	1400

/* copyNetUntilEOF
   copy chunks of data from a network file descriptor to a file pointer.
   No termination condition is expected except EOF on the input fd.
   This is a blocking read. */

void
copyNetUntilEOF(s, fp)
int	s;
FILE	*fp;
{
	char	buf[NET_BUFFER_SIZE];
	int	j;

	while (True) {
		j = read(s, buf, NET_BUFFER_SIZE);


		if (j <= 0) {
			if (j == -1)
			    fprintf (stderr,
				"Error (%d) copying data from the network\n",
					errno);
			break;
		}
		
		if (fwrite(buf, 1, j, fp) != j) {
			return;
		}
	}
}


/* giveCsoNameServer
   provide access to a CSO name server */

BOOLEAN
giveCsoNameServer(gi)
gopherItemP	gi;
{
	int		s;
	
	s = connectToSocket(gi->host, gi->port);
	if (s < 0) {
		/* could not make a network connection to receive file */
		networkError(s, gi->host, gi->port);
		return FALSE;
	}

	showNameServer(USER_STRING(gi), s);

	/* important: descriptor s will be closed by the name server
	   panel when it is done.  This will allow that panel to be
	   left up and operate asychronously with the rest of xgopher. */

	return TRUE;
}


/* getIndexSearchString
   provide access to an Index Search */

BOOLEAN
getIndexSearchString(gi)
gopherItemP	gi;
{
	int		s;

	/* This call will put up the panel with a grabNonexclusive, so
	   that no other gopher activity can proceed until the user 
	   has taken some action on this panel (done/cancel/etc).
	   Any action will result in a callback that eventually invokes
	   processIndexSelection (below). */
	
	showIndex(gi);
}


/* processIndexSelection
   process the index transaction from the user's selected keyword string */

void
processIndexSelection(gi, string)
gopherItemP	gi;
char		*string;
{
	/* this function is similar to a "getDirectory" operation */


	showStatus("Awaiting search results", GSTAT_WAIT);
	getIndexDirectory(gi, string);
	checkPanelButtons();
	showStatus("select an item from the list", GSTAT_SELECT);
}


/* giveTelnetSession
   provide an Xterm session running a telnet command */

BOOLEAN
giveTelnetSession(gi)
gopherItemP	gi;
{
	char	telnetCmd[PATH_NAME_LEN];	
	char	message[MESSAGE_STRING_LEN];

	if (! appResources->allowTelnet ) {
		showError("Telnet sessions are not enabled for this session");
		return FALSE;
	}

	if (strlen ( appResources->telnetCommand ) <= 0 ) {
		showError("Cannot execute the telnet command");
		return FALSE;
	}

	if (gi->host == NULL || strlen ( gi->host ) <= 0 ) {
		showError("There is no host to telnet to.");
		return FALSE;
	}

	if (gi->port == 0 || gi->port == 23) 
		sprintf (telnetCmd, "%s %s &\n",
			appResources->telnetCommand, gi->host);
	else 
		sprintf (telnetCmd, "%s %s %d &\n",
			appResources->telnetCommand, gi->host, gi->port);
	
	system(telnetCmd);

	if (strlen(vStringValue(&(gi->selector))) > 0) {
	    sprintf(message,
		"If a login name is required, try:\n\'%s\'",
		vStringValue(&(gi->selector)));
	    showInfo(message);
	}

	return TRUE;
}


/* giveTn3270Session
   provide an Xterm session running a tn3270 (or x3270) command */

BOOLEAN
giveTn3270Session(gi)
gopherItemP	gi;
{
	char	tn3270Cmd[PATH_NAME_LEN];	
	char	message[MESSAGE_STRING_LEN];

	if (! appResources->allowTn3270 ) {
		showError("Tn3270 sessions are not enabled for this session");
		return FALSE;
	}

	if (strlen ( appResources->tn3270Command ) <= 0 ) {
		showError("Cannot execute the tn3270 command");
		return FALSE;
	}

	if (gi->host == NULL || strlen ( gi->host ) <= 0 ) {
		showError("There is no host to telnet (tn3270) to.");
		return FALSE;
	}

	if (gi->port == 0 || gi->port == 23) 
		sprintf (tn3270Cmd, "%s %s &\n",
			appResources->tn3270Command, gi->host);
	else 
		sprintf (tn3270Cmd, "%s %s %d &\n",
			appResources->tn3270Command, gi->host, gi->port);
	
	system(tn3270Cmd);

	if (strlen(vStringValue(&(gi->selector))) > 0) {
	    sprintf(message,
		"If a login name is required, try:\n\'%s\'",
		vStringValue(&(gi->selector)));
	    showInfo(message);
	}

	return TRUE;
}


/* copySelectionToFile
   process a copy-item-to-file request */

BOOLEAN
copySelectionToFile(gi)
gopherItemP	gi;
{
	BOOLEAN	result=FALSE;

	switch (gi->type) {
	case A_FILE:
		showStatus("copying a text file to your file", GSTAT_WAIT);
		result = copyItemToFile(gi, copyAscii);

		break;

	case A_DIRECTORY:
		showError("You cannot copy a directory to a file.");
		result = FALSE;
		
		break;

	case A_CSO:
		showError("You cannot copy a CSO name server to a file.");
		result = FALSE;
		break;

	case A_ERROR:
		
		break;

	case A_INDEX:
		showError("You cannot copy an index search to a file.");
		result = FALSE;

		break;

	case A_TELNET:
		showError(
		    "You cannot copy a telnet session reference to a file.");
		result = FALSE;
		
		break;

	case A_TN3270:
		showError(
		    "You cannot copy a tn3270 session reference to a file.");
		result = FALSE;

		break;

	case A_BINARY:
		showStatus("copying a binary file to your file", GSTAT_WAIT);
		result = copyItemToFile(gi, copyBin);
		
		break;

	case A_IMAGE:
		showStatus("copying an image file to your file", GSTAT_WAIT);
		result = copyItemToFile(gi, copyBin);
		
		break;

	case A_SOUND:
		showStatus("copying a sound file to your file", GSTAT_WAIT);
		result = copyItemToFile(gi, copyBin);
		
		break;

	case A_EOI:
		
		break;

	default:
		{
		char message[MESSAGE_STRING_LEN];

		sprintf(message,
	   "There is no standard support for this type of gopher item (%c).\n",
				gi->type);
		strcat(message,
			"The data will be copied as binary to your file.");
		showInfo(message);
		result = copyItemToFile(gi, copyBin);
		break;
		}
	}

	showStatus("select an item from the list", GSTAT_SELECT);
	return result;
}


/* processSelection
   process the gopher transaction of the user's selected item */

BOOLEAN
processSelection(gi)
gopherItemP	gi;
{
	BOOLEAN	result=FALSE;

	switch (gi->type) {
	case A_FILE:
		showStatus("getting text file", GSTAT_WAIT);

		/* index string will be null for text file in normal dir */

		result = getFile(gi, getCurrentDirIndex());

		break;

	case A_DIRECTORY:
		showStatus("getting requested directory", GSTAT_WAIT);
		result = getDirectory(gi);
		
		break;

	case A_CSO:
		/* display panel.  Since the panel can operate independent
		   and asynchronous of the rest of gopher, no special
		   grabs are used. */

		showStatus("starting name server", GSTAT_WAIT);
		result = giveCsoNameServer(gi);
		break;

	case A_ERROR:
		
		break;

	case A_INDEX:
		{ 
		    char *ind = getItemIndex(gi);

		    if (ind == NULL) {

			/* normal case: no search string previously
			   selected.  Display panel; after string is selected,
			   it is passed to processIndexSelection. */

			showStatus("Enter an index search string", GSTAT_WAIT);
			result = getIndexSearchString(gi);

		    } else {
			/* if bookmark index search string is already stored
			   in the gopher item.  No need to prompt for it. */
			
			showStatus("Awaiting search results", GSTAT_WAIT);
			getIndexDirectory(gi, NULL);
			checkPanelButtons();
		    }
		}

		break;

	case A_TELNET:
		showStatus("starting a telnet session window", GSTAT_WAIT);
		result = giveTelnetSession(gi);

		break;

	case A_TN3270:
		showStatus("starting a tn3270 session window", GSTAT_WAIT);
		result = giveTn3270Session(gi);

		break;

	case A_IMAGE:
		showStatus("Retrieving image file for display", GSTAT_WAIT);
		result = getImage(gi);

		break;

	case A_SOUND:
		/* once it looks like we can really do sounds, the
		   sound process is forked off and executed
		   as a separate process.  However, only one sound
		   process may be active at a time.  This is enforced
		   by getSound. */

		showStatus("trying to start playing a sound file", GSTAT_WAIT);
		result = getSound(gi);
		
		break;

	case A_BINARY:
		if (appResources->allowCopy) {
			showStatus("Copying a binary file to your file",
					GSTAT_WAIT);
			result = copyItemToFile(gi, copyBin);
		} else {
			char message[MESSAGE_STRING_LEN];

			sprintf(message,
	    "Sorry, copying files is disallowed for this Xgopher session.");
			showError(message);
		}
		
		break;

	case A_EOI:
		
		break;

	default:
		{
		char message[MESSAGE_STRING_LEN];

		sprintf(message,
	    "There is no automatic processing for this gopher item type (%c)",
		    gi->type);
		strcat(message,
	    "\nYou may be able to use the Copy command to access the file.");
		showError(message);
		break;
		}
	}

	showStatus("select an item from the list", GSTAT_SELECT);
	return result;
}
