/*****************************************************************************
 * File:	jughead.c
 *
 * Author:	Rhett "Jonzy" Jones
 *		jonzy@cc.utah.edu
 *
 * Date:	February 17, 1993
 *
 * Modified:	February 27, 1993 by Rhett "Jonzy" Jones
 *		To support both the 'h' and 'v' flags, and to no longer
 * 		write recurrsivly to temporary files.  Now I only write
 *		to a single temporary file.
 *
 *		March 3, 1993, by Rhett "Jonzy" Jones
 *		Added support for the 'b' flag.
 *
 *		March 10, 1993, by Rhett "Jonzy" Jones.
 *		Added support for the 'B' and 'l' flags.
 *
 *		March 13, 1993, by Rhett "Jonzy" Jones.
 *		Fixed some bugs, optimized some routines, and rewrote
 *		DoSystemCall().  Removed Lowercase() and changed calls
 *		calls to this routine to StrToLower(), which is defined
 *		"search.c".  Modified BeenHereB4() to handle the cases
 *		where the selector string is "/" and "1/", which is the
 *		same as "".
 *
 *		March 15, 1993, by Rhett "Jonzy" Jones.
 *		Relocated the changes made to BeenHereB4() to a new
 *		routine EmptyStringCheck() so all occurances of "/",
 *		"1/", and "1" get mapped to "".  I should mention I just
 *		discovered "1" gets handled like "/", "1/", and "".  Hmm.
 *
 *		March 18, 1993, by Rhett "Jonzy" Jones.
 *		Added HostField2Lower() to make sure the host field gets
 *		mapped to lowercase, which is needed when building the
 *		index so that sort will eliminate all duplicates.
 *		Fixed the system call to "sort" to only sort on the selector
 *		string and beyond.  Also defined CATCOMMAND, RMCOMMAND, and
 *		SORTCOMMAND for use when calling DoSystemCall().
 *
 *		March 21, 1993, by Rhett "Jonzy" Jones.
 *		GetParamsFromStdin() now only prints the message informing
 *		the user to enter control-D when finished, only if we are
 *		getting the information from stdin and not a file.
 *
 * Description:	Jonzy's Universal Gopher Hierarchy Excavation And Display.
 *		Excavates through gopher menus and displays the hierarchy
 *		of the menus encountered.
 *
 * Routines:	void	UsageError(char *progName);
 *		void	PrintVersion(char *progName);
 *		short	GetParamsFromStdin(void);
 *		void	InitPath(void);
 *		int	GetArguments(int  argc,char *argv[],short *buildIndex,
 *				     short *doSearch,char **fileName,char **logFile);
 *		short	Host2Search(char *s);
 *		List	*CreateNode(char *sStr,char *hStr,char *pStr);
 *		List	*InsertNode(List *tail,List *node);
 *		List	*RemoveNode(List *tail);
 *		short	BeenHereB4(char *sStr,char *hStr,char *pStr,List *head);
 *		short	DoWeRecurse(char type,char *sStr,char *hStr,char *pStr,List *head);
 *		void	PrintPath(char *p,int indent);
 *		void	PrintItem(char *dStr,int indent);
 *		void	BuildPath(char *dStr,int indent);
 *		void	DestroyPath(int indent);
 *		char	*EmptyStringCheck(char *sStr);
 *		char	*HostField2Lower(char *line);
 *		void	ProcessMenu(char *selStr,char *hostStr,char *portStr,int indent);
 *		int	main(int argc,char *argv[]);
 *
 * Bugs:	No known bugs.
 *
 * Copyright:	Copyright 1993, University of Utah Computer Center.
 *		This source may be freely distributed as long as this copyright
 * 		notice remains intact, and is in no way used for any monetary
 * 		gain, by any institution, business, person, or persons.
 *
 ****************************************************************************/

#ifdef NEXT
#	include <libc.h>
#else
#	include <stdlib.h>
#endif
#include <signal.h>
#include <stdio.h>
#include <strings.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>		/* Needed for the isatty() call. */

#include "tree.h"
#include "utils.h"

/* These defines were taken from "GSgopherobj.h". */
#define A_FILE      '0'      /* Types of objects */
#define A_DIRECTORY '1'
#define A_CSO       '2'
#define A_ERROR     '3'
#define A_MACHEX    '4'
#define A_PCBIN     '5'
#define A_INDEX     '7'
#define A_TELNET    '8'
#define A_UNIXBIN   '9'
#define A_SOUND     's'
#define A_EVENT     'e'
#define A_CALENDAR  'c'
#define A_GIF       'g'
#define A_HTML      'h'
#define A_TN3270    'T'
#define A_MIME      'M'
#define A_IMAGE     'I'
#define A_EOI	    '.'

#define VERSION		"0.2_beta"		/* The version of this program. */

#define CATCOMMAND	"cat %s >> %s"
#define RMCOMMAND	"rm -f %s"

/* This is the system dependent call to "sort".  It needs to be a call to sort	*
 * such that only sort on text after the first tab character.  In this sort	*
 * command the first "%s" is the name of the resultant sorted file, and the	*
 * second "%s" is the name of the file to have sorted.  The "-T /tmp" says	*
 * to use /tmp as the place for any temporary files.  You may want to have	*
 * the temporary files written to /tmp.  The use of sort isnot to sort the data	*
 * but to remove any duplicates that may have been written to the file.		*/
#define SORTCOMMAND	"sort -d -f -u -o %s -t'	' -T /tmp +1.0 %s"

#define BUFFERSIZE	2048			/* The size of our buffer. */
#define DEFAULTPORT	"70"			/* The default port to use. */
#define EMPTYSTRING	""			/* The empty C string. */
#define FIRSTMENU	0			/* The level of indention. */
#define INDENTSTR	"    "			/* What we indent with. */
#define MAXHOSTS	50			/* The maximum number of hosts2search. */
#define NIL		(void *)0L		/* Same as NULL, but ... Oh well. */
#define TMPFILENAME	"/tmp/jughead.tmp"	/* The temporary files name. */

#define YEP		'1'			/* The pathway as been printed. */
#define NOPE		'\0'			/* The pathway has not been printed. */

typedef struct { char	*sStr,			/* The selector string. */
			*hStr,			/* The host string. */
			*pStr;			/* The port string. */
	       } ListInfo;			/* List info about the host or message. */

typedef struct node { ListInfo	  info;		/* The info contained at this node. */
		      struct node *next,	/* Pointer to the next node. */
				  *last;	/* The last node before this one. */
		    } List;

extern char *StrToLower(char *str);		/* Defined in "search.c". */

FILE	*wtPtr,			/* File pointer for writing to 'theHost'. */
	*rdPtr;			/* File pointer for reading from 'theHost'. */
char	buffer[BUFFERSIZE],	/* The output from the machine. */
	path[BUFFERSIZE],	/* The path to print if using 'onlyDoHostsT'. */
	pathPrinted[50],	/* Did we print the path(s) if using 'onlyDoHostsT'. */
	*searchHosts[MAXHOSTS],	/* Hosts to search. */
	*initialHost,		/* The initial host we connected to. */
	*selStr = 0,		/* The selector string. */
	*hostStr = 0,		/* The host string. */
	*portStr = 0,		/* The port string. */
	*fileName = NIL,	/* Name of the file to open or create. */
	*exceptionHost = NIL,	/* The host not to traverse. */
	*exceptionPort = NIL;	/* The port for the host not to traverse. */
int	numSearchHosts,		/* The number of hosts to search. */
	debug = 0,		/* Are we debugging? */
	info4dirsOnly = 0,	/* Do we print host info for directories only? */
	info4allItems = 0,	/* Do we print host info for all the items? */
	listHosts = 0,		/* Do we print only the hosts accessed? */
	listHostsNPorts = 0,	/* Do we print only the hosts and ports accessed? */
	onlyDoHosts = 0,	/* Do we print info for selected hosts only? */
	onlyDoHostsT = 0,	/* Same as onlyDoHosts but traverse anyway. */
	buildDataFile = 0;	/* Are we building the dataFile? */
List	*head = NIL,		/* The head of the list. */
	*tail = NIL;		/* The tail of the list. */


extern char	*logFile;	/* Name of the file to log to. */

/*****************************************************************************
 * UsageError prints the usage of the program and terminates.
 ****************************************************************************/
static void UsageError(progName)
	char	*progName;	/* Name of the program. */
{	int	numSpaces;	/* Number of spaces to print or the second
				 * part of the main usage line. */

	fprintf(stderr,"usage: %s [-dhHiIoOv] [-b dataFile] [-s selStr] host [-p port#]\n",progName);
	for (numSpaces = strlen("usage: ") + strlen(progName); numSpaces; numSpaces--)
		fprintf(stderr," ");
	fprintf(stderr," [-x hostNot2search port#] [- | hosts...]\n");
	fprintf(stderr,"usage: %s -B[dv] dataFile\n",progName);
	fprintf(stderr,"usage: %s -S[dv] indexFile [-l log]\n",progName);

}	/* UsageError */

/*****************************************************************************
 * PrintVersion prints the version of the program and terminates.
 ****************************************************************************/
void PrintVersion(progName)
	char	*progName;		/* The name of this program. */
{
	fprintf(stderr,"This version of %s is %s\n",progName,VERSION);
	exit(0);

}	/* PrintVersion */

/*****************************************************************************
 * GetParamsFromStdin gets words from stdin until end-of-file is
 * encounterd.  This routine also takes care of backspaces.
 ****************************************************************************/
short GetParamsFromStdin()
{	int	c,		/* A character from stdin. */
		i;		/* A loop counter. */
	short	done = 0;	/* Are we done yet? */
	char	*s;		/* The string we are building. */

	if (isatty(STDIN_FILENO))
		{
		fprintf(stderr,"Please enter the hosts you would like to traverse\n");
		fprintf(stderr,"separated by whitespace.  When you are finished\n");
		fprintf(stderr,"entering the hosts enter ^D [control-d].\n  --> ");
		}

	for (i = 0; (c = fgetc(stdin)) && !done && numSearchHosts < MAXHOSTS; i++)
		if (isspace(buffer[i] = (char)c) || c == EOF)
			{
			if (c == EOF)
				done = 1;
			buffer[i] = '\0';
			if (i)
				if (s = (char *)malloc(i * sizeof(char)))
					searchHosts[numSearchHosts++] = strcpy(s,buffer);
				else
					{
					fprintf(stderr,"error: could not get memory for [%s]\n",buffer);
					exit(-2);
					}
			i = -1;
			}
		else if (c == '\b')	/* Take care of backspace. */
			{
			i -= 2;
			if (i < -1)
				i = -1;
			}

	if (numSearchHosts >= MAXHOSTS)
		fprintf(stderr,"warning: only the first %d hosts2search are being used.\n",MAXHOSTS);

	return(1);

}	/* GetParamsFromStdin */

/*****************************************************************************
 * InitPath resets the all path variables to zero.
 ****************************************************************************/
void InitPath()
{	int	i;

	for (i = 0; i < 50; i++)
		pathPrinted[i] = NOPE;
	path[0] = '\0';

}	/* InitPath */

/*****************************************************************************
 * GetArguments returns true if we were able to initialize variables from
 * argc and argv.  Otherwise this routine calls UsageError() which exits, if
 * something was wrong with the way the arguments were passed in.
 ****************************************************************************/
int GetArguments(argc,argv,buildIndex,doSearch,fileName,logFile)
	int	argc;
	char	*argv[];
	short	*buildIndex,	/* Do we build the index table? */
		*doSearch;	/* Are we doing the search stuff? */
	char	**fileName,	/* Name of the file to open or create. */
		**logFile;	/* Name of the file to log to if searching. */
{	short	usageError = 0;	/* Do we have a usage error? */
	char	*s;		/* A temporary string. */
	int	i,		/* A loop counter. */
		flags = 0,	/* The 'd', 'h', or 'H' flag argv. */
		useStdin = 0;	/* Do we use stdin to get hosts2search? */

	selStr = EMPTYSTRING;
	portStr = DEFAULTPORT;
	*logFile = NIL;

	if (argc > 1)
		if (argv[1][0] == '-')
			if (strchr(argv[1],'v'))
				PrintVersion(argv[0]);
			else for (flags++, s = argv[1] + 1; *s; s++)
				switch(*s)
					{
					case 'B':
						*buildIndex = 1;
						break;
					case 'b':
						if (argc > 3)
							{
							buildDataFile = 1;
							*fileName = argv[1 + flags++];
							}
						else
							usageError = 1;
						break;
					case 'd':
						debug = 1;
						break;
					case 'H':
						listHosts = listHostsNPorts = 1;
						break;
					case 'h':
						listHosts = 1;
						break;
					case 'I':
						info4allItems = 1;
						break;
					case 'i':
						info4dirsOnly = 1;
						break;
					case 'O':
						onlyDoHosts = onlyDoHostsT = 1;
						InitPath();
						break;
					case 'o':
						onlyDoHosts = 1;
						break;
					case 'S':
						*doSearch = 1;
						break;
					case 's':
						if (argc > 3)
							selStr = argv[1 + flags++];
						else
							usageError = 1;
						break;
					default:
						usageError = 1;
					}
		else
			*buildIndex = buildDataFile = debug = listHostsNPorts =
			listHosts = info4allItems = info4dirsOnly = onlyDoHosts =
			onlyDoHostsT = onlyDoHosts = *doSearch = 0;
	else
		usageError = 1;

	if (!usageError && argc >= 2 && (*buildIndex || *doSearch))
		{
		if (*buildIndex && argc != 3)
			{
			fprintf(stderr,"error: 'B' flag requires the name of the data to build the index table.\n");
			usageError = 1;
			}
		if (*doSearch && !(argc == 3 || argc == 5))
			{
			fprintf(stderr,"error: 'S' flag requires the name of the index table built with the 'B' flag.\n");
			usageError = 1;
			}
		if (*buildIndex && *doSearch)
			{
			fprintf(stderr,"error: Please select either the 'B' or 'S' flag, not both.\n");
			*buildIndex = *doSearch = 0;
			usageError = 1;
			}
		if (!usageError)
			{
			*fileName = argv[2];
			if (argc == 5 && *doSearch && !strcmp("-l",argv[3]))
				*logFile = argv[4];
			}
		}
	else if (!usageError && !*buildIndex && !*doSearch)
		{
		if (argc > 4 && !*fileName && !strcmp("-b",argv[1 + flags]))
			{
			buildDataFile = 1;
			*fileName = argv[1 + ++flags];
			flags++;
			}
		if (argc > (4 + flags) && !selStr[0] && !strcmp("-s",argv[1 + flags]))
			{
			selStr = argv[1 + ++flags];
			flags++;
			}

		if (!usageError && argc > 1 + flags)
			{
			initialHost = hostStr = argv[1 + flags];

			/* Check if the user wants to use a non-standard gopher port. */
			if (argc >= (4 + flags) && !strcmp(argv[2 + flags],"-p"))
				{
				portStr = argv[2 + ++flags];
				flags++;
				}

			/* Check if we have any traverse exceptions. */
			if (argc > (4 + flags) && !strcmp(argv[2 + flags],"-x"))
				{
				exceptionHost = argv[2 + ++flags];
				exceptionPort = argv[2 + ++flags];
				flags++;
				}

			/* Make sure we have a valid port number. */
			for (s = portStr; *s && !usageError; s++)
				if (!isdigit(*s))
					usageError = 1;

			/* Make sure we have a valid exceptions port number. */
			if (exceptionPort)
				for (s = portStr; *s && !usageError; s++)
					if (!isdigit(*s))
						usageError = 1;

			/* Check if we have any hosts2search parameters and if so from where. */
			if (!usageError && argc > (2 + flags))
				if (argv[2 + flags][0] == '-')
					if (strlen(argv[2 + flags]) == 1)
						GetParamsFromStdin();
					else
						usageError = 1;
				else for (numSearchHosts = 0; numSearchHosts < (argc - (2 + flags)); numSearchHosts++)
					searchHosts[numSearchHosts] = argv[(2 + flags) + numSearchHosts];

			/* If no hosts to search just search the host. */
			if (!usageError && !numSearchHosts)
				searchHosts[numSearchHosts++] = hostStr;
			}
		else
			usageError = 1;
		}

	if (!usageError && debug)	/* Post some debugging information. */
		{
		if (flags)
			fprintf(stderr,"flags are [%c%c%c%c%c%c%c%c%c%c%c]",
					buildDataFile	? 'b' : '\0',
					*buildIndex	? 'B' : '\0',
					debug		? 'd' : '\0',
					listHosts	? 'h' : '\0',
					listHostsNPorts	? 'H' : '\0',
					info4dirsOnly	? 'i' : '\0',
					info4allItems	? 'I' : '\0',
					*logFile	? 'l' : '\0',
					onlyDoHosts	? 'o' : '\0',
					onlyDoHostsT	? 'O' : '\0',
					*doSearch	? 'S' : '\0');

		if (!*doSearch && !*buildIndex)	/* No sense in posting this information. */
			{
			fprintf(stderr,"%sselStr = [%s], host = [%s], port = [%s]\n",
					flags ? ", " : "",selStr,hostStr,portStr);
			if (exceptionHost && exceptionPort)
				fprintf(stderr,"exception host and port = %s and %s\n",exceptionHost,exceptionPort);
			if (numSearchHosts)
				{
				fprintf(stderr,"hosts2search =\n");
				for (i = 0; i < numSearchHosts; i++)
					fprintf(stderr,"\t[%s]\n",searchHosts[i]);
				}
			}
		else
			fprintf(stderr,"\n");
		}

	if (!usageError && (*buildIndex || *doSearch) &&
			   (buildDataFile || listHosts     || listHostsNPorts ||
			    info4dirsOnly || info4allItems || onlyDoHosts     ||
			    onlyDoHostsT))
		fprintf(stderr,"warning: [%c%c%c%c%c%c%c] has no effect with [%c]\n",
				buildDataFile	? 'b' : '\0',
				listHosts	? 'h' : '\0',
				listHostsNPorts	? 'H' : '\0',
				info4dirsOnly	? 'i' : '\0',
				info4allItems	? 'I' : '\0',
				onlyDoHosts	? 'o' : '\0',
				onlyDoHostsT	? 'O' : '\0',
				*buildIndex	? 'B' : 'S');

	if (usageError)		/* Bad command line arguments. */
		{
		UsageError(argv[0]);
		return(0);
		}

	return(1);

}	/* GetArguments */

/*****************************************************************************
 * Host2Search returns true if the hosts 's' is a member of 'searchHosts',
 * otherwise it returns false.
 ****************************************************************************/
short Host2Search(s)
	char	*s;	/* Is this in 'searchHosts'? */
{	int	i;	/* A loop counter. */

	for (i = 0; i < numSearchHosts; i++)
		if (StrRcmp(searchHosts[i],s))
		/* if (!strcmp(s,searchHosts[i])) */
			return(1);
	return(0);

}	/* Host2Search */

/*****************************************************************************
 * CreateNode returns a newly created and initialized node for the list.
 ****************************************************************************/
List *CreateNode(sStr,hStr,pStr)
	char	*sStr,		/* The selector string. */
		*hStr,		/* The host string. */
		*pStr;		/* The port string. */
{	List	*node;		/* The node we are returning. */

	if (node = (List *)malloc(sizeof(List)))
		if (node->info.sStr = (char *)malloc(strlen(sStr) + 1))
			{
			strcpy(node->info.sStr, sStr);
			if (node->info.hStr = (char *)malloc(strlen(hStr) + 1))
				{
				strcpy(node->info.hStr, hStr);

				if (node->info.pStr = (char *)malloc(strlen(pStr) + 1))
					{
					strcpy(node->info.pStr, pStr);
					node->next = node->last = NIL;
					return(node);
					}
				else
					{
					free(node->info.hStr);	node->info.hStr = NIL;
					free(node->info.sStr);	node->info.sStr = NIL;
					free(node);		node = NIL;
					}
				}
			else
				{
				free(node->info.sStr);	node->info.sStr = NIL;
				free(node);		node = NIL;
				}
			}
		else
			{
			free(node);	node = NIL;
			}

	fprintf(stderr,"error: CreateNode could not get memory for the node.\n");
	return(node);

}	/* CreateNode */

/*****************************************************************************
 * InsertNode inserts the node 'node' on the end of the list 'list'.
 ****************************************************************************/
List *InsertNode(tail,node)
	List	*tail,		/* The tail of the list we are inserting into. */
		*node;		/* The node we are inserting. */
{
	if (!node)
		return(tail);

	tail->next = node;
	node->last = tail;
	return(node);

}	/* InsertNode */

/*****************************************************************************
 * RemoveNode removes and frees up the memory occupied by the last item
 * in the list, which is pointed to by 'tail'.
 ****************************************************************************/
List *RemoveNode(tail)
	List	*tail;		/* The tail of the list we are inserting into. */
{	List	*node;		/* The node to remove. */

	node = tail;
	tail = tail->last;
	tail->next = NIL;
	free(node->info.sStr);		node->info.sStr = NIL;
	free(node->info.hStr);		node->info.hStr = NIL;
	free(node->info.pStr);		node->info.pStr = NIL;
	free(node);			node = NIL;
	return(tail);

}	/* RemoveNode */

/*****************************************************************************
 * BeenHereB4 returns true if 'sStr', 'hStr', and 'pStr' are in the list
 * pointed to by 'head'.  This is done so we will not travese a menu
 * we are already looking at.  Otherwise it returns false because we
 * are not looking at nor have been in this part of the tree.
 ****************************************************************************/
short BeenHereB4(sStr,hStr,pStr,head)
	char	*sStr,		/* The selector string. */
		*hStr,		/* The host string. */
		*pStr;		/* The port string. */
	List	*head;		/* Head of the list. */
{
	while (head)		/* See if we have been here before. */
		{
		if (!strcmp(head->info.sStr,sStr))
			if (!strcmp(head->info.hStr,hStr))
				if (!strcmp(head->info.pStr,pStr))
					return(1);
		head = head->next;
		}
	return(0);

}	/* BeenHereB4 */

/*****************************************************************************
 * DoWeRecurse returns true if we can look at this directory in the menu.
 * Otherwise it returns false.  In otherwords return true if the item we
 * have not looked at this directory yet, and the current entry is a
 * directory, and the directory is from one of the hosts contained in
 * 'hosts2search'.
 ****************************************************************************/
short DoWeRecurse(type,sStr,hStr,pStr,head)
	char	type,		/* What type of entry is this? */
 		*sStr,		/* The selector string. */
		*hStr,		/* The host string. */
		*pStr;		/* The port string. */
	List	*head;		/* Head of the list. */
{
	if (exceptionHost && exceptionPort)
		if (!strcmp(hStr,exceptionHost) && !strcmp(hStr,exceptionHost))
			return(0);

	if (type == A_DIRECTORY)
		if (Host2Search(hStr) || (onlyDoHostsT && !strcmp(initialHost,hStr)))
			return(!BeenHereB4(sStr,hStr,pStr,head));

	return(0);

}	/* DoWeRecurse */

/*****************************************************************************
 * PrintPath prints the pathway, with the proper number of indentions for
 * an item that has not had the pathway printed.
 ****************************************************************************/
void PrintPath(p,indent)
	char	*p;		/* The path to print. */
	int	indent;		/* The indention level. */
{	char	*s,		/* The string to print out. */
		*t;		/* A temp string. */
	int	i,j;		/* Loop counters. */
	short	gotOne = 0; 	/* Do we have a directory to print? */

	/* Find the directory we need to start printing if we have one. */
	for (i = 0; i < indent; i++)
		if (pathPrinted[i] == NOPE)
			{
			gotOne = 1;
			break;
			}

	if (gotOne)	/* Print the pathway. */
		{
		/* Skip pathways that have been printed. */
		for (j = 0, s = p; j < i; j++)
			{
			for ( ; *s && *s != '\t'; s++)
				;
			if (*s)
				s++;
			}

		/* Print the unprinted pathway(s). */
		for ( ; i < indent; i++)
			{
			if (i)		/* Indent the directory. */
				fprintf(stdout,"%s",INDENTSTR);
			for ( ; *s && *s != '\t'; s++)
				fprintf(stdout,"%c",*s);
			if (*s)
				s++;
			pathPrinted[i] = YEP;
			fprintf(stdout,"/\n");
			}
		}

}	/* PrintPath */

/*****************************************************************************
 * PrintItem prints the item with the proper number of indentions, as well
 * as prints what type of item it is just like the unix gopher from Minnesota.
 ****************************************************************************/
void PrintItem(dStr,indent)
	char	*dStr;		/* The display string. */
	int	indent;		/* The level of indention. */
{	int	numIndents;	/* The number of indentions to add to the path. */

	/* Indent the data 'indent' number of indentions. */
	for (numIndents = 0; numIndents < indent; numIndents++)
		fprintf(stdout,"%s",INDENTSTR);

	/* Print the menu title like a gopher menu. */
	fprintf(stdout,"%s",dStr + 1);
	switch (dStr[0])
		{
		case A_DIRECTORY:
			fprintf(stdout,"/");
			if (onlyDoHostsT)
				pathPrinted[indent] = YEP;
			break;
		case A_CSO:
			fprintf(stdout," <CSO>");
			break;
		case A_TN3270:
			fprintf(stdout," <3270>");
			break;
		case A_TELNET:
			fprintf(stdout," <TEL>");
			break;
		case A_INDEX:
			fprintf(stdout," <?>");
			break;
		case A_SOUND:
			fprintf(stdout," <)");
			break;
		case A_FILE:
			fprintf(stdout,".");
			break;
		case A_PCBIN:
			fprintf(stdout," <PC Bin>");
			break;
		case A_UNIXBIN:
			fprintf(stdout," <Bin>");
			break;
		case A_IMAGE:
		case A_GIF:
			fprintf(stdout," <Picture>");
			break;
		case A_MACHEX:
			fprintf(stdout," <HQX>");
			break;
		default:
			fprintf(stdout," <~%c~>",dStr[0]);
			break;
		}

}	/* PrintItem */

/*****************************************************************************
 * BuildPath builds the pathways for items so we can print the items with
 * the pathway when the 'O' option is used.
 ****************************************************************************/
void BuildPath(dStr,indent)
	char	*dStr;		/* The string to add to the path. */
	int	indent;		/* The level of indention. */
{
	if (indent)
		strcat(path,"\t");
	strcat(path,dStr);

}	/* BuildPath */

/*****************************************************************************
 * DestroyPath destroys the last path within the pathway to an item.
 ****************************************************************************/
void DestroyPath(indent)
	int	indent;		/* The level of indention. */
{	char	*s;		/* A pointer into 'path'. */

	if (!indent)
		InitPath();
	else if ((s = strrchr(path,'\t')) > path)
		{
		*s = '\0';
		pathPrinted[indent] = NOPE;
		}

}	/* DestroyPath */

/*****************************************************************************
 * EmptyStringCheck checks if the string 'sStr' is either "/", "1/", or "1",
 * and if it is we return the emptystring "".  Otherwise 'sStr' gets returned
 * unchanged.  The reason for this routine is to prevent a recursive menu
 * loop from happening.  The gopher protocol says you must send the empty
 * string to acquire the menu.  Well it looks like a unix server handles
 * "", "/", "1/", and "1" all the same.  Hmm.
 ****************************************************************************/
static char *EmptyStringCheck(sStr)
	char	*sStr;		/* The string we are testing. */
{

	if (!strcmp(sStr,"/") || !strcmp(sStr,"1/") || !strcmp(sStr,"1"))
		return(EMPTYSTRING);
	return(sStr);

}	/* EmptyStringCheck */

/*****************************************************************************
 * HostField2Lower returns the string passed in with the host field converted
 * to lowercase.
 ****************************************************************************/
char *HostField2Lower(line)
	char	*line;		/* The line we are looking at. */
{	char	*s;		/* A position in 'line'. */

	for (s = strchr(strchr(line,'\t') + 1,'\t') + 1; *s && *s != '\t'; s++)
		*s = tolower(*s);

	return(line);

}	/* HostField2Lower */

/*****************************************************************************
 * ProcessMenu processes the menu entity which should be a directory.  If
 * the entity is not a directory ... well you know what they say, "garabage
 * in ... garbage out.
 * This code is ugly, but oh well such is life.
 ****************************************************************************/
void ProcessMenu(selStr,hostStr,portStr,indent)
	char	*selStr,	/* Selection string to send. */
		*hostStr,	/* The host to contact. */
		*portStr;	/* The port to use. */
	int	indent;		/* The level of indentation. */
{	char	**data,		/* The data acquired from the host via the file. */
		*done,		/* Are we done yet? */
		*rsltLine,	/* The resultant line of data. */
		*dStr,		/* The display field. */
		*sStr,		/* The selector field. */
		*hStr,		/* The host field. */
		*pStr;		/* The port field. */
	FILE	*fPtr;		/* Pointer to the menu file. */
	int	numItems,	/* Number of items in the menu. */
		error = 0,	/* Did we encounter an error? */
		i;		/* A loop counter. */

	if (!head)
		head = tail = CreateNode(selStr,hostStr,OnlyDigits(portStr));
	else
		tail = InsertNode(tail,CreateNode(selStr,hostStr,OnlyDigits(portStr)));

	if (ContactHost(hostStr,Str2Int(portStr)))
		{
		/* Write the information to a temporary file. */
		if (fPtr = fopen(TMPFILENAME,"w"))
			{
			SendString(selStr);
			SendString("\r\n");
			for (numItems = 0; !(done = strstr(rsltLine = GetString(rdPtr),".\r")); numItems++)
				fprintf(fPtr,"%s",(buildDataFile) ? HostField2Lower(rsltLine) : rsltLine);
			CloseReadNwriter();
			fclose(fPtr);
			if (buildDataFile)
				DoSystemCall(CATCOMMAND,TMPFILENAME,fileName);
			if (debug)
				fprintf(stderr,"Connections are now closed\n");
			}
		else
			error = fprintf(stderr,"error: ProcessMenu cannot create [%s]\n",TMPFILENAME);

		/* Read the temporary file into a dynamic array. */
		if (!error && (data = (char **)malloc(numItems * sizeof(char *))))
			{
			if (fPtr = fopen(TMPFILENAME,"r"))
				{
				for (i = 0; i < numItems && !error; i++)
					if (data[i] = (char *)malloc((strlen(rsltLine = GetString(fPtr)) + 1) * sizeof(char)))
						strcpy(data[i],rsltLine);
					else
						{
						error = fprintf(stderr,"error: ProcessMenu cannot get memory for the %dth element\n");
						for ( ; i; i--)
							free(data[i]);
						free(data);
						}
				fclose(fPtr);
				DoSystemCall(RMCOMMAND,TMPFILENAME);
				}
			else
				error = fprintf(stderr,"error: ProcessMenu cannot read [%s]\n",TMPFILENAME);
			}
		else if (!error && numItems)
			error = fprintf(stderr,"error: ProcessMenu cannot get memory for the %d data items\n",numItems);

		if (!error)
			{
			for (i = 0; i < numItems; i++)
				{
				dStr = MyStrTok(data[i],'\t');
				sStr = EmptyStringCheck(MyStrTok(NULL,'\t'));
				hStr = StrToLower(MyStrTok(NULL,'\t'));
				pStr = OnlyDigits(MyStrTok(NULL,'\0'));

				if (debug)
					{
					fprintf(stderr,"\tdStr = [%s]\n",dStr);
					fprintf(stderr,"\tsStr = [%s]\n",sStr);
					fprintf(stderr,"\thStr = [%s]\n",hStr);
					fprintf(stderr,"\tpStr = [%s]\n",pStr);
					}

				if (listHosts)
					{
					char	s[1024];
					sprintf(s,"%s\t%s",hStr,pStr);
					BuildTree(&root,s,-1);
					}
				else if (!onlyDoHosts || (onlyDoHosts && Host2Search(hStr)))
					{
					if (onlyDoHostsT)
						PrintPath(path,indent);

					PrintItem(dStr,indent);

					/* Do we print the host and port to the right of the item? */
					if (info4allItems || (info4dirsOnly && dStr[0] == A_DIRECTORY))
						fprintf(stdout,"\t%s %s",hStr,pStr);
					fprintf(stdout,"\n");
					}

				if (DoWeRecurse(dStr[0],sStr,hStr,pStr,head))
					{
					if (!listHosts && onlyDoHostsT)
						BuildPath(dStr + 1,indent);

					ProcessMenu(sStr,hStr,pStr,indent + 1);

					if (!listHosts &&  onlyDoHostsT)
						DestroyPath(indent);
					}
				}
			/* Free up the memory we acquired. */
			for (i = 0; i < numItems; i++)
				free(data[i]);
			free(data);
			}
		}

	if (head != tail)
		tail = RemoveNode(tail);

}	/* ProcessMenu */

/*****************************************************************************
 * main is the heart of this program.
 ****************************************************************************/
int main(argc,argv)
	int	argc;
	char	*argv[];
{	short	buildIndex = 0,	/* Do we build the index table? */
		doSearch = 0,	/* Are we doing the search stuff? */
		usageError = 0;	/* Do we have a usage error? */

	if (GetArguments(argc,argv,&buildIndex,&doSearch,&fileName,&logFile))
		if (doSearch)
			DoSearch(fileName,logFile);
		else if (buildIndex)
			if (CreateWordsTree(fileName))
				{
				fprintf(stdout,"%s\t%ld\n",fileName,NumberOfLeafs(root));
				PrintTree(root,0);
				}
			else
				exit(-1);
		else
			{
			ProcessMenu(selStr,hostStr,portStr,FIRSTMENU);
			if (buildDataFile)
				{
				char	sortedFileName[512];
				DoSystemCall(SORTCOMMAND,strcat(strcpy(sortedFileName,fileName),".sorted"),fileName);
				fprintf(stderr,"%s now contains all data sorted on column 1 with duplicates removed.\n",fileName);
				}
			else if (listHosts || listHostsNPorts)
				PrintTree(root,listHosts + listHostsNPorts);
			}

	exit(0);

}	/* main */
