/*
 	FetchAE.cp -- sample Apple Event code for Fetch
 	
 	Copyright © 1996, Trustees of Dartmouth College
 	Copyright (c) 2001, Fetch Softworks
 	All Rights Reserved.
 	
 	Distribution and use of this code is hereby permitted, provided that any resulting
 	work includes the copyright statement above.
*/

#include <string.h>

#include <AEObjects.h>
#include <AEPackObject.h>

#include "FetchAE.h"

static void InitDesc(AEDesc *desc);
static void DisposeDesc(AEDesc *desc);
static void CStringToDesc(char *valuep, DescType dType, AEDesc *desc);
static void DescTypeToDesc(OSType value, DescType dType, AEDesc *desc);
static void LongToDesc(long value, DescType dType, AEDesc *desc);
static void FSSpecToDesc(FSSpec *valuep, DescType dType, AEDesc *desc);
static Boolean findProcessBySignature(OSType sig, ProcessSerialNumber *psn_p, long *proc_mode_p);
static Boolean findAppBySignature(OSType sig, FSSpec *fspec_p);
static OSErr InitAEToApp(OSType appSig, AEEventClass eventClass, AEEventID eventID, 
			AppleEvent *aevt, Boolean *running);
static OSErr SendAEToApp(OSType appSig, AppleEvent *aevt, AppleEvent *aereply, AEIdleUPP idleUPP, Boolean running);
static OSErr SendDirectAppleEvent(OSType appSig, AEEventClass eventClass, AEEventID eventID,
				AEDesc *directDesc, AppleEvent *aereply, AEIdleUPP idleUPP);
static OSErr SendDirectAppleEventWithArg(OSType appSig, AEEventClass eventClass, AEEventID eventID,
				AEDesc *directDesc, AEKeyword argKey, AEDesc *argDesc,
				AppleEvent *aereply, AEIdleUPP idleUPP);
static OSErr SendDirectAppleEventWithThreeArgs(OSType appSig, AEEventClass eventClass, AEEventID eventID,
				AEDesc *directDesc, AEKeyword argKey, AEDesc *argDesc,
				AEKeyword arg2Key, AEDesc *arg2Desc,
				AEKeyword arg3Key, AEDesc *arg3Desc,
				AppleEvent *aereply, AEIdleUPP idleUPP);
static OSErr SendCreateAppleEvent(OSType appSig, DescType objClass, AEDesc *insertLocDesc,
				AEDesc *dataDesc, AEDesc *propDesc, 
				AppleEvent *aereply, AEIdleUPP idleUPP);
static OSErr SendMoveCloneAppleEvent(OSType appSig, AEEventID eventID, AEDesc *directDesc, 
				AEDesc *insertLocDesc, AppleEvent *aereply, AEIdleUPP idleUPP);
static OSErr CreateFormNameOSpec(char *name, DescType objClass, AEDesc *containerDesc, AEDesc *ospecDesc);
static OSErr CreatePropertyOSpec(DescType propCode, AEDesc *containerDesc, AEDesc *ospecDesc);

/*
 * InitDesc() --
 *	Initialize a descriptor.
 */
void InitDesc(AEDesc *desc)
{
	desc->descriptorType = typeNull;
	desc->dataHandle = nil;
}

/*
 * DisposeDesc() --
 *	Dispose of descriptor data and mark descriptor as empty.
 */
void DisposeDesc(AEDesc *desc)
{
	if (desc->dataHandle != nil)
		(void) AEDisposeDesc(desc);
	InitDesc(desc);
}

/*
 * CStringToDesc() -- 
 *	Put a C string into a descriptor record.
 */
void CStringToDesc(char *valuep, DescType dType, AEDesc *desc)
{
	desc->descriptorType = dType;
	if (dType == typeChar)
		(void) PtrToHand(valuep, &desc->dataHandle, strlen(valuep));
	else if (dType == typeIntlText)
	{
		desc->dataHandle = NewHandle(4+valuep[0]);
		*(short *) *desc->dataHandle = smRoman;
		*(short *) (*desc->dataHandle + 2) = langEnglish;
		BlockMoveData(valuep, *desc->dataHandle + 4, strlen(valuep));
	}
	else
		InitDesc(desc);
} /* CStringToDesc() */

/*
 * DescTypeToDesc() -- 
 *	Put an OSType into a descriptor record.
 */
void DescTypeToDesc(OSType value, DescType dType, AEDesc *desc)
{
	desc->descriptorType = dType;
	(void) PtrToHand(&value, &desc->dataHandle, sizeof(value));
} /* DescTypeToDesc() */

/*
 * LongToDesc() -- 
 *	Put a longint into a descriptor record.
 */
void LongToDesc(long value, DescType dType, AEDesc *desc)
{
	desc->descriptorType = dType;
	if (dType == typeLongDateTime)
	{
		desc->dataHandle = NewHandle(8);
		*(long *) *desc->dataHandle = 0;
		*(long *) (*desc->dataHandle + 4) = value;
	}
	else
		(void) PtrToHand(&value, &desc->dataHandle, sizeof(value));
} /* LongToDesc() */

/*
 * FSSpecToDesc() -- 
 *	Put a File System Spec into a descriptor record.
 */
void FSSpecToDesc(FSSpec *valuep, DescType dType, AEDesc *desc)
{
	OSErr	osstat;
	
	desc->descriptorType = dType;
	if (dType == typeFSS)
		(void) PtrToHand(valuep, &desc->dataHandle, sizeof(FSSpec));
	else if (dType == typeAlias)
	{
		osstat = NewAlias(nil, valuep, (AliasHandle *) &desc->dataHandle);
		if (osstat != noErr)
			InitDesc(desc);
	}
	else
		InitDesc(desc);
} /* FSSpecToDesc() */

/*
 * findProcessBySignature() -- 
 *	Find a running process based on its signature
 *	See IM-VI 29-11 
 */
Boolean findProcessBySignature(OSType sig, ProcessSerialNumber *psn_p, long *proc_mode_p)
{
	ProcessInfoRec		procInfo;

	psn_p->highLongOfPSN = 0;
	psn_p->lowLongOfPSN = kNoProcess;
	
	procInfo.processInfoLength = sizeof(procInfo);
	procInfo.processName = NULL;
	procInfo.processAppSpec = NULL;
	
	while (GetNextProcess(psn_p) == noErr)
		if (GetProcessInformation(psn_p, &procInfo) == noErr)
			if ((procInfo.processType == 'APPL') &&
				(procInfo.processSignature == sig))
			{
				*proc_mode_p = procInfo.processMode;
				return true;
			}
				
	return false;
} /* findProcessBySignature() */

/*
 * findAppBySignature() -- 
 *	Use the desktop database to find an app with this sig on a mounted volume.
 */
Boolean findAppBySignature(OSType sig, FSSpec *fspec_p)
{
	ParamBlockRec		pb;
	HParamBlockRec		hpb;
	GetVolParmsInfoBuffer	volParms;
	DTPBRec			dtpb;
	OSErr			osstat;

	/* for each volume */
	pb.volumeParam.ioCompletion = NULL;
	pb.volumeParam.ioNamePtr = NULL;
	pb.volumeParam.ioVRefNum = 0;
	pb.volumeParam.ioVolIndex = 1;
	osstat = PBGetVInfo(&pb, false);
	while (osstat == noErr)
	{
		/* check to see if it supports the desktop manager */
		hpb.ioParam.ioCompletion = NULL;
		hpb.ioParam.ioVRefNum = pb.volumeParam.ioVRefNum;
		hpb.ioParam.ioNamePtr = NULL;
		hpb.ioParam.ioBuffer = (Ptr) &volParms;
		hpb.ioParam.ioReqCount = sizeof(volParms);
		osstat = PBHGetVolParms(&hpb, false);
		if ((osstat == noErr) && (volParms.vMAttrib & (1 << bHasDesktopMgr)))
		{
			/* find out the Desktop reference number */
			dtpb.ioNamePtr = NULL;
			dtpb.ioVRefNum = pb.volumeParam.ioVRefNum;
			osstat = PBDTGetPath(&dtpb);
			if (osstat == noErr)
			{
				/* search this Desktop database for an app with the right signature */
				dtpb.ioCompletion = NULL;
				dtpb.ioNamePtr = (StringPtr) &(fspec_p->name);
				dtpb.ioIndex = 0;
				dtpb.ioFileCreator = sig;
				osstat = PBDTGetAPPL(&dtpb, false);
				if (osstat == noErr)
				{
					/* found the app */
					fspec_p->vRefNum = pb.volumeParam.ioVRefNum;
					fspec_p->parID = dtpb.ioAPPLParID;
					return true;
				} /* PBDTGetAPPL == noErr */
			} /* PBDTGetPath == noErr */
		} /* PBHGetVolParms == noErr */
	
		pb.volumeParam.ioVolIndex++;
		osstat = PBGetVInfo(&pb, false);
	} /* while PBGetVInfo == noErr */
	
	return false;
} /* findAppBySignature() */

/*
 * InitAEToApp() -- 
 *	Initialize an apple event to be sent to an application.  Return the event, and
 *	whether the application is currently running.
 */
OSErr InitAEToApp(OSType appSig, AEEventClass eventClass, AEEventID eventID, 
			AppleEvent *aevt, Boolean *running)
{
	AEDesc			addrDesc;
	ProcessSerialNumber	psn;
	long			dummy;
	OSErr			osstat;

	InitDesc(&addrDesc);
	osstat = noErr;
	
	*running = findProcessBySignature(appSig, &psn, &dummy);
	if (*running)
		osstat = AECreateDesc(typeProcessSerialNumber, &psn, sizeof(psn), &addrDesc);
	else
		osstat = AECreateDesc(typeApplSignature, &appSig, sizeof(OSType), &addrDesc);
	if (osstat != noErr) 
		goto exit;
	
	osstat = AECreateAppleEvent(eventClass, eventID, &addrDesc, 
				kAutoGenerateReturnID, kAnyTransactionID,
				aevt);
exit:
	DisposeDesc(&addrDesc);
	return osstat;
} /* InitAEToApp() */

/*
 * SendAEToApp() --
 *	Send an Apple Event to an application, which may or may not be running.  If it isn't
 *	running, the event reply won't be filled in....
 */
OSErr SendAEToApp(OSType appSig, AppleEvent *aevt, AppleEvent *aereply, AEIdleUPP idleUPP, Boolean running)
{
	LaunchParamBlockRec	lpb;
	AEDesc			appParmDesc;
	AEDesc			replyDesc;
	AppleEvent		myreply;
	AppleEvent		*replyp;
	FSSpec			appFspec;
	OSErr			osstat = noErr;
	
	InitDesc(&appParmDesc);
	InitDesc(&myreply);
	InitDesc(&replyDesc);
	
	if (running)
	{
		/* the app is running, just send the event */
		replyp = aereply ? aereply : &myreply;
		osstat = AESend(aevt, replyp, kAEWaitReply + kAECanInteract + kAECanSwitchLayer + kAEDontReconnect,
				kAENormalPriority, kAEDefaultTimeout, idleUPP, nil);
		if (osstat == -603)
			/* ignore this error, it doesn't seem to matter */
			osstat = noErr;
		if (osstat == noErr)
		{
			osstat = AEGetParamDesc(replyp, keyErrorNumber, typeLongInteger, &replyDesc);
			if (osstat == noErr)
				osstat = **(long **) replyDesc.dataHandle;
			else if (osstat == errAEDescNotFound)
				/* no error code, no error */
				osstat = noErr;
		}
	} // running
	else if (findAppBySignature(appSig, &appFspec))
	{
		/* we need to launch the app with the event */
		osstat = AECoerceDesc(aevt, typeAppParameters, &appParmDesc);
		if (osstat != noErr)
			goto exit;
		
		HLock(appParmDesc.dataHandle);
		
		/* call LaunchApplication */
		lpb.launchBlockID = extendedBlock;
		lpb.launchEPBLength = extendedBlockLen;
		lpb.launchFileFlags = launchNoFileFlags;
		lpb.launchControlFlags = launchContinue + launchDontSwitch;
		lpb.launchAppSpec = &appFspec;
		lpb.launchAppParameters = (AppParametersPtr) *appParmDesc.dataHandle;
		THz	oldZone = GetZone();
		osstat = LaunchApplication(&lpb);
		if (osstat == -603)
			/* ignore this error, it doesn't seem to matter */
			osstat = noErr;
		SetZone(oldZone);
	} // findAppBySignature()
	else
		/* app can't be found */
		osstat = fnfErr;

exit:
	DisposeDesc(&appParmDesc);
	DisposeDesc(&myreply);
	DisposeDesc(&replyDesc);
	return osstat;
} /* SendAEToApp() */

/*
 * SendDirectAppleEvent() -- 
 *	Send a simple event w/ just a direct parameter.
 */
OSErr SendDirectAppleEvent(OSType appSig, AEEventClass eventClass, AEEventID eventID,
				AEDesc *directDesc, AppleEvent *aereply, AEIdleUPP idleUPP)
{
	AppleEvent	aevt;
	OSErr		osstat;
	Boolean		running;

	InitDesc(&aevt);
	osstat = noErr;
	
	/* create the event */
	osstat = InitAEToApp(appSig, eventClass, eventID, &aevt, &running);
	if (osstat != noErr) 
		goto exit;
		
	/* add direct object */
	osstat = AEPutParamDesc(&aevt, keyDirectObject, directDesc);
	if (osstat != noErr) 
		goto exit;
		
	/* send the event */
	osstat = SendAEToApp(appSig, &aevt, aereply, idleUPP, running);
	
exit:
	DisposeDesc(&aevt);
	return osstat;
} /* SendDirectAppleEvent */

/*
 * SendDirectAppleEventWithArg() -- 
 *	Send a simple event w/ a direct parameter and one other argument.
 */
OSErr SendDirectAppleEventWithArg(OSType appSig, AEEventClass eventClass, AEEventID eventID,
				AEDesc *directDesc, AEKeyword argKey, AEDesc *argDesc,
				AppleEvent *aereply, AEIdleUPP idleUPP)
{
	AppleEvent	aevt;
	OSErr		osstat;
	Boolean		running;

	InitDesc(&aevt);
	osstat = noErr;
	
	/* create the event */
	osstat = InitAEToApp(appSig, eventClass, eventID, &aevt, &running);
	if (osstat != noErr) 
		goto exit;
		
	/* add direct object */
	osstat = AEPutParamDesc(&aevt, keyDirectObject, directDesc);
	if (osstat != noErr) 
		goto exit;
		
	/* add argument */
	osstat = AEPutParamDesc(&aevt, argKey, argDesc);
	if (osstat != noErr) 
		goto exit;
		
	/* send the event */
	osstat = SendAEToApp(appSig, &aevt, aereply, idleUPP, running);
	
exit:
	DisposeDesc(&aevt);
	return osstat;
} /* SendDirectAppleEventWithArg */

/*
 * SendDirectAppleEventWithThreeArgs() -- 
 *	Send ourselves an event w/ a direct parameter and three other arguments.
 */
OSErr SendDirectAppleEventWithThreeArgs(OSType appSig, AEEventClass eventClass, AEEventID eventID,
				AEDesc *directDesc, AEKeyword argKey, AEDesc *argDesc,
				AEKeyword arg2Key, AEDesc *arg2Desc,
				AEKeyword arg3Key, AEDesc *arg3Desc,
				AppleEvent *aereply, AEIdleUPP idleUPP)
{
	AppleEvent	aevt;
	OSErr		osstat;
	Boolean		running;

	InitDesc(&aevt);
	osstat = noErr;
	
	/* create the event */
	osstat = InitAEToApp(appSig, eventClass, eventID, &aevt, &running);
	if (osstat != noErr) 
		goto exit;
		
	/* add direct object */
	osstat = AEPutParamDesc(&aevt, keyDirectObject, directDesc);
	if (osstat != noErr) 
		goto exit;
		
	/* add arguments */
	osstat = AEPutParamDesc(&aevt, argKey, argDesc);
	if (osstat != noErr) 
		goto exit;
	osstat = AEPutParamDesc(&aevt, arg2Key, arg2Desc);
	if (osstat != noErr) 
		goto exit;
	osstat = AEPutParamDesc(&aevt, arg3Key, arg3Desc);
	if (osstat != noErr) 
		goto exit;
				
	/* send the event */
	osstat = SendAEToApp(appSig, &aevt, aereply, idleUPP, running);
	
exit:
	DisposeDesc(&aevt);
	return osstat;
} /* SendDirectAppleEventWithThreeArgs */

/*
 * SendCreateAppleEvent() -- 
 *	Send a Create Element event.
 */
OSErr SendCreateAppleEvent(OSType appSig, DescType objClass, AEDesc *insertLocDesc,
				AEDesc *dataDesc, AEDesc *propDesc, 
				AppleEvent *aereply, AEIdleUPP idleUPP)
{
	AppleEvent	aevt;
	OSErr		osstat;
	Boolean		running;

	InitDesc(&aevt);
	osstat = noErr;
	
	/* create the event */
	osstat = InitAEToApp(appSig, kAECoreSuite, kAECreateElement, &aevt, &running);
	if (osstat != noErr) 
		goto exit;
		
	/* add desired class */
	osstat = AEPutParamPtr(&aevt, keyAEObjectClass, typeType, &objClass, 
							sizeof(objClass));
	if (osstat != noErr)
		goto exit;
		
	/* add insertion location */
	osstat = AEPutParamDesc(&aevt, keyAEInsertHere, insertLocDesc);
	if (osstat != noErr) 
		goto exit;
		
	/* add initial data, if (there is some */
	if (dataDesc && (dataDesc->descriptorType != typeNull))
	{
		osstat = AEPutParamDesc(&aevt, keyAEData, dataDesc);
		if (osstat != noErr)
			goto exit;
	}
	
	/* add initial property values, if (there are some */
	if (propDesc && (propDesc->descriptorType != typeNull))
	{
		osstat = AEPutParamDesc(&aevt, keyAEPropData, propDesc);
		if (osstat != noErr)
			goto exit;
	}
	
	/* send the event */
	osstat = SendAEToApp(appSig, &aevt, aereply, idleUPP, running);
	
exit:
	DisposeDesc(&aevt);
	return osstat;
} /* SendCreateAppleEvent() */

/*
 * SendMoveCloneAppleEvent() -- 
 *	Send a Move or Clone event.
 */
OSErr SendMoveCloneAppleEvent(OSType appSig, AEEventID eventID, AEDesc *directDesc, 
				AEDesc *insertLocDesc, AppleEvent *aereply, AEIdleUPP idleUPP)
{
	AppleEvent	aevt;
	OSErr		osstat;
	Boolean		running;

	InitDesc(&aevt);
	osstat = noErr;
	
	/* create the event */
	osstat = InitAEToApp(appSig, kAECoreSuite, eventID, &aevt, &running);
	if (osstat != noErr) 
		goto exit;
		
	/* add direct object */
	osstat = AEPutParamDesc(&aevt, keyDirectObject, directDesc);
	if (osstat != noErr) 
		goto exit;
		
	/* add insertion location */
	osstat = AEPutParamDesc(&aevt, keyAEInsertHere, insertLocDesc);
	if (osstat != noErr) 
		goto exit;
		
	/* send the event */
	osstat = SendAEToApp(appSig, &aevt, aereply, idleUPP, running);
	
exit:
	DisposeDesc(&aevt);
	return osstat;
} /* SendMoveCloneAppleEvent() */

/* 
 * CreateFormNameOSpec() --
 *	Create a formName object specifier.
 */
OSErr CreateFormNameOSpec(char *name, DescType objClass, AEDesc *containerDesc, AEDesc *ospecDesc)
{
	AEDesc	keyDesc;
	AEDesc	nullDesc;
	
	InitDesc(&keyDesc);
	InitDesc(&nullDesc);
	CStringToDesc(name, typeChar, &keyDesc);
	OSErr	osstat = CreateObjSpecifier(objClass, containerDesc ? containerDesc : &nullDesc, 
						formName, &keyDesc, false, ospecDesc);
	DisposeDesc(&keyDesc);
	return osstat;
} // CreateFormNameOSpec()

/* 
 * CreatePropertyOSpec() --
 *	Create a formPropertyID object specifier.
 */
OSErr CreatePropertyOSpec(DescType propCode, AEDesc *containerDesc, AEDesc *ospecDesc)
{
	AEDesc	keyDesc;
	AEDesc	nullDesc;
	
	InitDesc(&keyDesc);
	InitDesc(&nullDesc);
	DescTypeToDesc(propCode, typeType, &keyDesc);
	OSErr	osstat = CreateObjSpecifier(cProperty, containerDesc ? containerDesc : &nullDesc, 
						formPropertyID, &keyDesc, false, ospecDesc);
	DisposeDesc(&keyDesc);
	return osstat;
} // CreatePropertyOSpec()

/*
 * FetchOpenURL() --
 *	Tell Fetch to open a directory (specified by a URL).
 */
OSErr FetchOpenURL(char *url, AEIdleUPP idleUPP)
{
	AEDesc	urlDesc;
	OSErr	osstat = noErr;
	
	InitDesc(&urlDesc);
	osstat = CreateFormNameOSpec(url, cURL, nil, &urlDesc);
	if (osstat == noErr)
		osstat = SendDirectAppleEvent(FETCH_APP_SIGNATURE, kCoreEventClass, kAEOpen, &urlDesc,
						nil, idleUPP);
	DisposeDesc(&urlDesc);
	return osstat;
} // FetchOpenURL()


/*
 * FetchPut() --
 *	Tell Fetch to upload a file or folder to a URL
 */
OSErr FetchPut(FSSpec *fspec, char *url, AEIdleUPP idleUPP)
{
	AEDesc	urlDesc;
	AEDesc	fspecDesc;
	OSErr	osstat = noErr;
	
	InitDesc(&urlDesc);
	InitDesc(&fspecDesc);
	
	osstat = CreateFormNameOSpec(url, cURL, nil, &urlDesc);
	if (osstat != noErr)
		goto exit;
	FSSpecToDesc(fspec, typeAlias, &fspecDesc);
	
	osstat = SendDirectAppleEventWithArg(FETCH_APP_SIGNATURE, kAEFetchSuite, kAEPutIntoEvent, 
							&urlDesc,
							kAEFetchPutIntoItemsParam, &fspecDesc,
							nil, idleUPP);
exit:
	DisposeDesc(&urlDesc);
	DisposeDesc(&fspecDesc);
	return osstat;
} // FetchPut()

/*
 * FetchGet() --
 *	Tell Fetch to download a file or folder from a URL to a folder
 */
OSErr FetchGet(char *url, FSSpec *fspec, AEIdleUPP idleUPP)
{
	AEDesc		nullDesc;
	AEDesc		aerDesc;
	AEDesc		insertLocDesc;
	AEDesc		fspecDesc;
	AEDesc		urlDesc;
	DescType	positionCode;
	OSErr		osstat;

	InitDesc(&nullDesc);
	InitDesc(&aerDesc);
	InitDesc(&insertLocDesc);
	InitDesc(&fspecDesc);
	InitDesc(&urlDesc);
	osstat = noErr;
	
	/* first get Ospec for target folder */
	FSSpecToDesc(fspec, typeAlias, &fspecDesc);
	
	/* next build insertion location ("Beginning of alias foo") */
	osstat = AECreateList(nil, 0, true, &aerDesc);
	if (osstat != noErr)
		goto exit;
		
	/* add object-in-relation-to field */
	osstat = AEPutKeyDesc(&aerDesc, keyAEObject, &fspecDesc);
	if (osstat != noErr)
		goto exit;
		
	/* add positioning field */
	positionCode = kAEBeginning;
	osstat = AEPutKeyPtr(&aerDesc, keyAEPosition, typeEnumerated, &positionCode,
				sizeof(positionCode));
	if (osstat != noErr)
		goto exit;
		
	/* coerce to typeInsertionLoc */
	osstat = AECoerceDesc(&aerDesc, typeInsertionLoc, &insertLocDesc);
	if (osstat != noErr)
		goto exit;
		
	/* create Ospec for item to get */
	osstat = CreateFormNameOSpec(url, cURL, nil, &urlDesc);
	if (osstat != noErr)
		goto exit;
		
	/* now send the event */
	osstat = SendMoveCloneAppleEvent(FETCH_APP_SIGNATURE, kAEClone, &urlDesc, 
						&insertLocDesc, nil, idleUPP);
	
exit:	/* clean up */
	DisposeDesc(&nullDesc);
	DisposeDesc(&aerDesc);
	DisposeDesc(&insertLocDesc);
	DisposeDesc(&urlDesc);
	DisposeDesc(&fspecDesc);
	return osstat;
} // FetchGet()

/*
 * FetchViewFile() --
 *	Tell Fetch to view a file (specified by a URL).
 */
OSErr FetchViewFile(char *url, AEIdleUPP idleUPP)
{
	AEDesc	urlDesc;
	OSErr	osstat = noErr;
	
	InitDesc(&urlDesc);
	osstat = CreateFormNameOSpec(url, cURL, nil, &urlDesc);
	if (osstat == noErr)
		osstat = SendDirectAppleEvent(FETCH_APP_SIGNATURE, kAEFetchSuite, kAEViewFileEvent, 
						&urlDesc, nil, idleUPP);
	DisposeDesc(&urlDesc);
	return osstat;
} // FetchViewFile()

/*
 * FetchItemExists() --
 *	Ask Fetch whether a file (or directory) is included in the frontmost transfer window file list.
 */
OSErr FetchItemExists(char *fname, Boolean *exists, AEIdleUPP idleUPP)
{
	AppleEvent	aereply;
	AEDesc		fnameDesc;
	AEDesc		replyDesc;
	OSErr		osstat = noErr;
	
	InitDesc(&aereply);
	InitDesc(&fnameDesc);
	InitDesc(&replyDesc);
	
	osstat = CreateFormNameOSpec(fname, cHostItem, nil, &fnameDesc);
	if (osstat != noErr)
		goto exit;
	
	osstat = SendDirectAppleEvent(FETCH_APP_SIGNATURE, kAECoreSuite, kAEDoObjectsExist, 
						&fnameDesc, &aereply, idleUPP);
	if (osstat != noErr)
		goto exit;
		
	osstat = AEGetParamDesc(&aereply, keyAEResult, typeBoolean, &replyDesc);
	if (osstat != noErr)
		goto exit;
	
	*exists = **replyDesc.dataHandle != 0;
	
exit:
	DisposeDesc(&aereply);
	DisposeDesc(&fnameDesc);
	DisposeDesc(&replyDesc);
	return osstat;
} // FetchItemExists()

/*
 * FetchRenameItem() --
 *	Rename an item in the frontmost transfer window file list.
 */
OSErr FetchRenameItem(char *oldname, char *newname, AEIdleUPP idleUPP)
{
	AEDesc		itemDesc;
	AEDesc		propDesc;
	AEDesc		newnameDesc;
	OSErr		osstat = noErr;
	
	InitDesc(&itemDesc);
	InitDesc(&propDesc);
	InitDesc(&newnameDesc);
	
	/* create ospec for name of named item */
	osstat = CreateFormNameOSpec(oldname, cHostItem, nil, &itemDesc);
	if (osstat != noErr)
		goto exit;
		
	osstat = CreatePropertyOSpec(pName, &itemDesc, &propDesc);
	if (osstat != noErr)
		goto exit;
		
	/* create desc for new name */
	CStringToDesc(newname, typeChar, &newnameDesc);
	
	/* send the event */
	osstat = SendDirectAppleEventWithArg(FETCH_APP_SIGNATURE, kAECoreSuite, kAESetData, 
						&propDesc, keyAEData, &newnameDesc, nil, idleUPP);
	if (osstat != noErr)
		goto exit;
	
exit:
	DisposeDesc(&itemDesc);
	DisposeDesc(&propDesc);
	DisposeDesc(&newnameDesc);
	return osstat;
} // FetchRenameItem()

/*
 * FetchCreateDirectory() --
 *	Tell Fetch to create a directory (in the frontmost transfer window).
 */
OSErr FetchCreateDirectory(char *dirname, AEIdleUPP idleUPP)
{
	AEDesc		nullDesc;
	AEDesc		aerDesc;
	AEDesc		insertLocDesc;
	AEDesc		propDesc;
	DescType	positionCode;
	OSErr		osstat;

	InitDesc(&nullDesc);
	InitDesc(&aerDesc);
	InitDesc(&insertLocDesc);
	InitDesc(&propDesc);
	osstat = noErr;
	
	/* first build insertion location ("Beginning of null container") */
	/* NOTE: null container equals the front window in this case */
	osstat = AECreateList(nil, 0, true, &aerDesc);
	if (osstat != noErr)
		goto exit;
		
	/* add object-in-relation-to field */
	osstat = AEPutKeyDesc(&aerDesc, keyAEObject, &nullDesc);
	if (osstat != noErr)
		goto exit;
		
	/* add positioning field */
	positionCode = kAEBeginning;
	osstat = AEPutKeyPtr(&aerDesc, keyAEPosition, typeEnumerated, &positionCode,
				sizeof(positionCode));
	if (osstat != noErr)
		goto exit;
		
	/* coerce to typeInsertionLoc */
	osstat = AECoerceDesc(&aerDesc, typeInsertionLoc, &insertLocDesc);
	if (osstat != noErr)
		goto exit;
		
	/* build initial property data */
	osstat = AECreateList(nil, 0, true, &propDesc);
	if (osstat != noErr)
		goto exit;
			
	/* add dir name (can't create dir without it) */
	osstat = AEPutKeyPtr(&propDesc, pName, typeChar, dirname, strlen(dirname));
	if (osstat != noErr)
		goto exit;

	/* now send the event */
	osstat = SendCreateAppleEvent(FETCH_APP_SIGNATURE, cHostDirectory, 
						&insertLocDesc, nil, &propDesc, nil, idleUPP);
	
exit:	/* clean up */
	DisposeDesc(&nullDesc);
	DisposeDesc(&aerDesc);
	DisposeDesc(&insertLocDesc);
	DisposeDesc(&propDesc);
	return osstat;
} // FetchCreateDirectory()

/*
 * FetchSendCommand() --
 *	Tell Fetch to send a custom command (or CR-delimited list of commands)
 *	to a URL (i.e. connect to the URL, then send the commands).
 */
OSErr FetchSendCommand(char *cmd, char *url, AEIdleUPP idleUPP)
{
	AEDesc	urlDesc;
	AEDesc	cmdDesc;
	OSErr	osstat = noErr;
	
	InitDesc(&urlDesc);
	InitDesc(&cmdDesc);
	
	osstat = CreateFormNameOSpec(url, cURL, nil, &urlDesc);
	if (osstat != noErr)
		goto exit;
	CStringToDesc(cmd, typeChar, &cmdDesc);
	
	osstat = SendDirectAppleEventWithArg(FETCH_APP_SIGNATURE, kAEFetchSuite, kAESendFTPCommandEvent, 
							&urlDesc,
							kAEFetchSendFTPCommandsParam, &cmdDesc,
							nil, idleUPP);
exit:
	DisposeDesc(&urlDesc);
	DisposeDesc(&cmdDesc);
	return osstat;
} // FetchSendCommand()

/*
 * FetchGetPreference() --
 *	Get an arbitrary Fetch preference.
 */
OSErr FetchGetPreference(DescType propCode, AEDesc *valueDesc, AEIdleUPP idleUPP)
{
	AppleEvent	aereply;
	AEDesc		nullDesc;
	AEDesc		propDesc;
	OSErr		osstat = noErr;
	
	InitDesc(&aereply);
	InitDesc(&nullDesc);
	InitDesc(&propDesc);
	
	/* create ospec for application object property (app object == null) */
	osstat = CreatePropertyOSpec(propCode, &nullDesc, &propDesc);
	if (osstat != noErr)
		goto exit;
		
	/* send the event */
	osstat = SendDirectAppleEvent(FETCH_APP_SIGNATURE, kAECoreSuite, kAEGetData, 
						&propDesc, &aereply, idleUPP);
	if (osstat != noErr)
		goto exit;
		
	osstat = AEGetParamDesc(&aereply, keyAEResult, typeWildCard, valueDesc);
	if (osstat != noErr)
		goto exit;
		
exit:
	DisposeDesc(&aereply);
	DisposeDesc(&nullDesc);
	DisposeDesc(&propDesc);
	return osstat;
} // FetchGetPreference()

/*
 * FetchSetPreference() --
 *	Set an arbitrary Fetch preference.
 */
OSErr FetchSetPreference(DescType propCode, AEDesc *valueDesc, AEIdleUPP idleUPP)
{
	AEDesc		nullDesc;
	AEDesc		propDesc;
	OSErr		osstat = noErr;
	
	InitDesc(&nullDesc);
	InitDesc(&propDesc);
	
	/* create ospec for application object property (app object == null) */
	osstat = CreatePropertyOSpec(propCode, &nullDesc, &propDesc);
	if (osstat != noErr)
		goto exit;
		
	/* send the event */
	osstat = SendDirectAppleEventWithArg(FETCH_APP_SIGNATURE, kAECoreSuite, kAESetData, 
						&propDesc, keyAEData, valueDesc, nil, idleUPP);
	if (osstat != noErr)
		goto exit;
	
exit:
	DisposeDesc(&nullDesc);
	DisposeDesc(&propDesc);
	return osstat;
} // FetchSetPreference()

