/* sc_extend.c
   gopher item subclass procedures for extended type */

     /*---------------------------------------------------------------*/
     /* Xgopher        version 1.3     08 April 1993                  */
     /*                version 1.2     20 November 1992               */
     /*                version 1.1     20 April 1992                  */
     /*                version 1.0     04 March 1992                  */
     /* 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, 1993 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 "osdep.h"
#include "conf.h"
#include "globals.h"
#include "gopher.h"
#include "appres.h"
#include "typeres.h"
#include "util.h"
#include "status.h"
#include "subst.h"
#include "misc.h"
#include "jobs.h"
#include "sc_extend.h"
#include "sc_extendP.h"

#include <stdio.h>
extern	 errno;

static extTypeValue	*etvList = NULL;


#define	PIPE_CHAR	'|'


/* forkAndExec
   fork off another process which will call system to execute a command */

static PID_TYPE
forkAndExec(cmd, tempName, forkErrno)
char	*cmd;
char	*tempName;
int	*forkErrno;
{
	PID_TYPE	pid;


	/* fprintf (stderr, "forkAndExec: %s\n", cmd); */

	*forkErrno = 0;
	if ((pid = fork()) == (PID_TYPE) 0) {

		/* we should immediately do a 
		   close (ConnectionNumber(XtDisplay(widget)));
		   here.  */
		   
		system(cmd);
		if (tempName != (char *) NULL) {
			unlink (tempName);
		}

		/* the child's useful life is over */

		exit(0);

	} else if (pid < 0) {
		*forkErrno = errno;
		if (tempName != (char *) NULL) {
			unlink (tempName);
		}
		pid = ((PID_TYPE) 0);
	}

	return pid;
}


/* forkAndPipe
   fork off another process which will pipe stdin to another command */

static PID_TYPE
forkAndPipe(cmd, gi, forkErrno)
char		*cmd;
gopherItemP	gi;
int		*forkErrno;
{
	char		message[MESSAGE_STRING_LEN];
	FILE		*cmdFile;
	int		s;
	PID_TYPE	pid;

	*forkErrno = 0;
	if ((pid = fork()) == (PID_TYPE) 0) {

		/* we should immediately do a 
		      close (ConnectionNumber(XtDisplay(widget)));
		   here.
		*/

		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);

		cmdFile = popen(cmd, "w");

		if (cmdFile == (FILE *) NULL) {
		    sprintf(message,
			"Unable to start the command (\'%s\')\n",
			    cmd);
			fprintf(stderr, "%s\n", message);
			close (s);
			/* this is the child process */
			exit(-1);
		}

		GI_copyNetUntilEOF(s, cmdFile);

		close(s);
		pclose(cmdFile);

		/* the child's useful life is over */

		exit(0);


	} else if (pid < 0) {
		*forkErrno = errno;
		pid = ((PID_TYPE) 0);
	}

	return pid;
}


BOOLEAN
issueCommand(gi, etv)
gopherItemP	gi;
extTypeValue	*etv;
{
	scInfo	*sc = gi->sc;
	char	*cmd;
	char	errorString[MESSAGE_STRING_LEN];
	char	tempName[PATH_NAME_LEN];
	char	message[MESSAGE_STRING_LEN];
	int	tempFD;
	int	s;
	char	*path, *suffix = (char *) NULL;


	/* Three cases: 1) no data to be copied (e.g. a telnet session);
			2) data copied to temp file first (e.g. text)
			3) data piped from network to program (e.g. sound)
	*/


	if (sc->copyProc == GI_noCopyItem) {
	    
	    /* ===== No Data needed ===== */

	    int	forkError;

	    cmd = editCommand(gi, etv->execCommand, "", (char *) NULL);
	    /* fprintf (stderr, "\tExecute the command\n\t%s\n\n", cmd); */

	    sprintf(message, "Working on %s, please stand by",
				gi->sc->typeName);
	    showStatus(message, STAT_TEMP_MESSAGE, (char *) NULL, 0);

	    if ((etv->pid =
		    forkAndExec(tildePath(cmd), (char *)NULL, &forkError))
			== 0) {
		etv->failed = TRUE;
		sprintf(message,
	        "Error trying to start command for \'%s\' (error %d)\n\'%s\'\n",
		    sc->typeName, errno, cmd);
		showError(message);
	    }

	    if (etv->pid != (PID_TYPE) 0) {
		addJob(gi->type, etv->pid);
	    }

	    if (etv->wait) {
		waitForJob(etv->pid);
	    }

	    free(cmd);

	} else if (*(etv->execCommand) != PIPE_CHAR) { 

	    /* ===== Data copied to temp file ===== */

	    int	forkError;
	    BOOLEAN okSoFar;

	    getTempFile(tempName);

	    /* find the file name suffix which may provide additional
	       info on file type to the implementor -- 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);
		}
	    }

	    if ((tempFD = open(tempName, O_WRONLY | O_CREAT, TMP_FILE_MODE))
									< 0) {
		perror("issueCommand");
		(void) removeStatusPanel();

		sprintf(errorString,
			"Cannot open the temporary file %s", tempName);
		showError(errorString);
		fprintf (stderr, "%s\n", errorString);
		return FALSE;
	    }
	
	    okSoFar = GI_copyFromNet(tempFD, gi, (char *) NULL);

	    close(tempFD);

	    if (! okSoFar) {
		    unlink (tempName);
		    return FALSE;
	    }

	    cmd = editCommand(gi, etv->execCommand, tempName, tempName);
	    /* fprintf (stderr, "\tExecute the command\n\t%s\n\n", cmd); */

	    sprintf(message, "Working on %s, please stand by",
				gi->sc->typeName);
	    showStatus(message, STAT_TEMP_MESSAGE, (char *) NULL, 0);

	    if ((etv->pid =
		    forkAndExec(tildePath(cmd), tempName, &forkError))
			== 0) {
		etv->failed = TRUE;
		sprintf(message,
	        "Error trying to start command for \'%s\' (error %d)\n\'%s\'\n",
		    sc->typeName, errno, cmd);
		showError(message);
	    }

	    if (etv->pid != (PID_TYPE) 0) {
		addJob(gi->type, etv->pid);
	    }

	    if (etv->wait) {
		waitForJob(etv->pid);
	    }

	    free(cmd);

	} else {				/* pipe data to command */
	    
	    /* ===== Data comes from a pipe ===== */

	    int	*forkError;

	    cmd = editCommand(gi, etv->execCommand, "", (char *) NULL);
	    /* fprintf (stderr, "\tExecute the command\n\t%s\n\n", cmd); */

	    sprintf(message, "Working on %s, please stand by",
				gi->sc->typeName);
	    showStatus(message, STAT_TEMP_MESSAGE, (char *) NULL, 0);

	    if ((etv->pid =
		    forkAndPipe(tildePath(cmd+1), gi, &forkError))
			== 0) {
		etv->failed = TRUE;
		sprintf(message,
	        "Error trying to start command for \'%s\' (error %d)\n\'%s\'\n",
		    sc->typeName, errno, cmd);
		showError(message);
	    }

	    if (etv->pid != (PID_TYPE) 0) {
		addJob(gi->type, etv->pid);
	    }

	    if (etv->wait) {
		waitForJob(etv->pid);
	    }

	    free(cmd);
	}

	return TRUE;
}


/* getExtTypeValue
   return the extended type value record for a given type letter, or
   NULL if it does not exist. */

static extTypeValue *
getExtTypeValue(wanted)
char	wanted;
{
	extTypeValue	*etv;

	for (etv=etvList;
		(etv != (extTypeValue *) NULL  &&  etv->type != wanted);
		etv=etv->next) ;

	return etv;
}


/* GIExtend_init
   initialize extend type class - host access list and prefix. */

void
GIExtend_init()
{
	char		*s = appResources->extendedTypes;
	char		extType[6];
	typeResources	*typeRes;
	scInfo		*scRec;
	char		message[MESSAGE_STRING_LEN];

	if (s == (char *) NULL  ||  *s == NULLC) {	
		return;
	}

	GU_makePrefix(prefixExtend,  appResources->prefixUnknown);

	strcpy(extType, "type ");
	while (*s != NULLC) {
	    if (*s == ' '  ||  *s == '\t') {s++; continue;}

	    /* process new type; receive back a static struct */

	    extType[4] = *s;
	    typeRes = getTypeResources(extType, EXT_CLASS);

	    /* look for "sameAs" - if NOT present, copy EXTEND subclass */

	    if (typeRes->sameAs == (String) NULL  ||
		    *(typeRes->sameAs) == NULLC) {
	    	extTypeValue	*etv;
	    	scRec = GU_copySubclassRecord(A_EXTENDED);

		/* copy execCommand, useTempFile, wait, singleThread
		   to a new record. */

		etv = (extTypeValue *) malloc (sizeof(extTypeValue));
		etv->type         = *s;
		if (typeRes->execCommand != (char *) NULL) {
			etv->execCommand  =
			    (char *) malloc (strlen(typeRes->execCommand) + 1);
			strcpy(etv->execCommand, typeRes->execCommand);
	    	} else {
	    		etv->execCommand = (char *) NULL;
	    	}
		etv->wait	  = typeRes->wait;
		etv->failed	  = FALSE;
		etv->pid	  = (PID_TYPE) 0;
		etv->next	  = etvList;

		etvList = etv;

	    /* "sameAs" given - use internal or extended type */

	    } else if (*(typeRes->sameAs + 1) != NULLC) {
	    	    sprintf (message,
			"Extended type \'%c\' - \'sameAs\' %s (not \'%s\')",
	    		*s, "resource must be a single character",
			typeRes->sameAs);
	    	    fprintf (stderr, "%s\n", message);
	    } else {
		extTypeValue	*oldEtv = getExtTypeValue(*(typeRes->sameAs));

		scRec = GU_copySubclassRecord(*(typeRes->sameAs));

		if (oldEtv != (extTypeValue *) NULL) {
		    extTypeValue	*etv;

		    /* copy extTypeValue to complete the "sameAs" processing */

		    etv = (extTypeValue *) malloc (sizeof(extTypeValue));
		    bcopy((char *) oldEtv, (char *) etv, sizeof(extTypeValue));

		    /* then, replace the type and execCommand
		       in the new record. */

		    etv->type         = *s;
		    if (typeRes->execCommand != (char *) NULL) {
		    	etv->execCommand  =
		    	    (char *) malloc (strlen(typeRes->execCommand) + 1);
		    	strcpy(etv->execCommand, typeRes->execCommand);
	    	    }

		    etv->failed	  = FALSE;
		    etv->pid	  = (PID_TYPE) 0;
		    etv->next	  = etvList;

		    etvList = etv;
	        }
	    }

	    if (typeRes->description != (String) NULL) {
	    	scRec->typeName = malloc(strlen(typeRes->description) + 1);
	    	strcpy(scRec->typeName, typeRes->description);
	    }
	    if (typeRes->prefix != (String) NULL) {
	    	scRec->typePrefix = (char *) malloc(PREFIX_LEN);
		GU_makePrefix(scRec->typePrefix, typeRes->prefix);
	    }
	    if (typeRes->servers != (String) NULL) {
	    	scRec->hostList    = (accessList *) malloc(sizeof(accessList));
	    	*(scRec->hostList) = GU_createAccessList(typeRes->servers);
	    }

	    if (typeRes->dataType != copyTypeUnspec) {
	        scRec->copyDataType = typeRes->dataType;
	    	if (scRec->copyDataType == copyTypeNone) {
		    scRec->copyProc = GI_noCopyItem;
	    	} else {
		    scRec->copyProc = GI_copyToFile;
	    	}
	    }

	    GU_registerNewType(*s, scRec);

	    s++;
	}

	return;
}


/* GIExtend_access
   check the accessability of an extended type file selection */

BOOLEAN
GIExtend_access(gi)
gopherItemP	gi;
{
	BOOLEAN	result;

	if (gi->sc->hostList == NULL  ||  *(gi->sc->hostList) == NULL  ||
	    GU_checkAccess(gi->host, *(gi->sc->hostList))) {
		result = TRUE;
	}else {
		result = FALSE;
	}

	return result;
}


/* GIExtend_process
   process an extended type file selection */

BOOLEAN
GIExtend_process(gi)
gopherItemP	gi;
{
	BOOLEAN		result;
	extTypeValue	*etv = getExtTypeValue(gi->type);
	char		message[MESSAGE_STRING_LEN];


	/* fprintf (stderr, "Process a type %c.\n", gi->type); */

	if (etv == (extTypeValue *) NULL) {
	 	return FALSE;
	}

	if (etv->execCommand == (char *) NULL  ||
	    strlen(etv->execCommand) == 0) {
		fprintf (stderr,
		     "Type %c item (%s) - nothing to do\n",
			gi->type, gi->sc->typeName);
		return FALSE;
	}

	/* fprintf (stderr, "Process a type %c.\n\tRaw command:\t%s\n",
			gi->type, etv->execCommand); */

	sprintf (message, "Trying to start %s %s",
		(GU_isVowel(gi->sc->typeName) ? "an" : "a"), gi->sc->typeName);

	showStatus(message, STAT_TEMP_MESSAGE, gi->host, gi->port);


	result = issueCommand(gi, etv); 
	
	return result;
}
