/*
**	Apple Macintosh Developer Technical Support
**
**	A collection of useful high-level Desktop Manager routines.
**	If the Desktop Manager isn't available, use the Desktop file
**	for 'read' operations.
**
**	We do more because we can...
**
**	by Jim Luther and Nitin Ganatra, Apple Developer Technical Support Emeriti
**
**	File:	MoreDesktopMgr.c
**
**	Copyright © 1992-1999 Apple Computer, Inc.
**	All rights reserved.
**
**	You may incorporate this sample code into your applications without
**	restriction, though the sample code has been provided "AS IS" and the
**	responsibility for its operation is 100% yours.  However, what you are
**	not permitted to do is to redistribute the source as "DSC Sample Code"
**	after having made changes. If you're going to re-distribute the source,
**	we require that you make it clear in the source that the code was
**	descended from Apple Sample Code, but that you've made changes.
*/

#include <Types.h>
#include <Errors.h>
#include <Memory.h>
#include <Files.h>
#include <Resources.h>
#include <Icons.h>

#define	__COMPILINGMOREFILES

#include "MoreFiles.h"
#include "MoreFilesExtras.h"
#include "Search.h"
#include "MoreDesktopMgr.h"

/*****************************************************************************/

/*	Desktop file notes:
**
**	¥	The Desktop file is owned by the Finder and is normally open by the
**		Finder. That means that we only have read-only access to the Desktop
**		file.
**	¥	Since the Resource Manager doesn't support shared access to resource
**		files and we're using read-only access, we don't ever leave the
**		Desktop file open.  We open a path to it, get the data we want out
**		of it, and then close the open path. This is the only safe way to
**		open a resource file with read-only access since some other program
**		could have it open with write access.
**	¥	The bundle related resources in the Desktop file are normally
**		purgable, so when we're looking through them, we don't bother to
**		release resources we're done looking at - closing the resource file
**		(which we always do) will release them.
**	¥	Since we can't assume the Desktop file is named "Desktop"
**		(it probably is everywhere but France), we get the Desktop
**		file's name by searching the volume's root directory for a file
**		with fileType == 'FNDR' and creator == 'ERIK'. The only problem with
**		this scheme is that someone could create another file with that type
**		and creator in the root directory and we'd find the wrong file.
**		The chances of this are very slim.
*/

/*****************************************************************************/

/* local defines */

enum
{
	kBNDLResType	= 'BNDL',
	kFREFResType	= 'FREF',
	kIconFamResType	= 'ICN#',
	kFCMTResType	= 'FCMT',
	kAPPLResType	= 'APPL'
};

/*****************************************************************************/

/* local data structures */

#if PRAGMA_STRUCT_ALIGN
#pragma options align=mac68k
#endif

struct IDRec
{
	short		localID;
	short		rsrcID;
};
typedef struct IDRec IDRec;
typedef	IDRec *IDRecPtr;

struct BundleType
{
	OSType		type;			/* 'ICN#' or 'FREF' */
	short		count;			/* number of IDRecs - 1 */
	IDRec		idArray[1];
};
typedef struct BundleType BundleType;
typedef BundleType *BundleTypePtr;

struct BNDLRec
{
	OSType		signature;		/* creator type signature */
	short		versionID;		/* version - should always be 0 */
	short		numTypes;		/* number of elements in typeArray - 1 */
	BundleType	typeArray[1];
};
typedef struct BNDLRec BNDLRec;
typedef BNDLRec **BNDLRecHandle;

struct FREFRec
{
	OSType		fileType;		/* file type */
	short		iconID;			/* icon local ID */
	Str255		fileName;		/* file name */
};
typedef struct FREFRec FREFRec;
typedef FREFRec **FREFRecHandle;

struct APPLRec
{
	OSType		creator;		/* creator type signature */
	long		parID;			/* parent directory ID */
	Str255		applName;		/* application name */
};
typedef struct APPLRec APPLRec;
typedef APPLRec *APPLRecPtr;

#if PRAGMA_STRUCT_ALIGN
#pragma options align=reset
#endif

/*****************************************************************************/

/* static prototypes */

static	OSErr	GetDesktopFileName(short vRefNum,
								   Str255 desktopName);

static	OSErr	GetAPPLFromDesktopFile(ConstStr255Param volName,
									   short vRefNum,
									   OSType creator,
									   short *applVRefNum,
									   long *applParID,
									   Str255 applName);

static	OSErr	FindBundleGivenCreator(OSType creator,
									   BNDLRecHandle *returnBndl);
									   
static	OSErr	FindTypeInBundle(OSType typeToFind,
								 BNDLRecHandle theBndl,
								 BundleTypePtr *returnBundleType);
										 
static	OSErr	GetLocalIDFromFREF(BundleTypePtr theBundleType,
								   OSType fileType,
								   short *iconLocalID);

static	OSErr	GetIconRsrcIDFromLocalID(BundleTypePtr theBundleType,
										 short iconLocalID,
										 short *iconRsrcID);

static	OSType	DTIconToResIcon(short iconType);

static	OSErr	GetIconFromDesktopFile(ConstStr255Param volName,
									   short vRefNum,
									   short iconType,
									   OSType fileCreator,
									   OSType fileType,
									   Handle *iconHandle);

static	OSErr	GetCommentID(short vRefNum,
							 long dirID,
							 ConstStr255Param name,
							 short *commentID);

static	OSErr	GetCommentFromDesktopFile(short vRefNum,
										  long dirID,
										  ConstStr255Param name,
										  Str255 comment);

/*****************************************************************************/

/*
**	GetDesktopFileName
**
**	Get the name of the Desktop file.
*/
static	OSErr	GetDesktopFileName(short vRefNum,
								   Str255 desktopName)
{
	OSErr			error;
	HParamBlockRec	pb;
	short			index;
	Boolean			found;
	
	pb.fileParam.ioNamePtr = desktopName;
	pb.fileParam.ioVRefNum = vRefNum;
	pb.fileParam.ioFVersNum = 0;
	index = 1;
	found = false;
	do
	{
		pb.fileParam.ioDirID = fsRtDirID;
		pb.fileParam.ioFDirIndex = index;
		error = PBHGetFInfoSync(&pb);
		if ( error == noErr )
		{
			if ( (pb.fileParam.ioFlFndrInfo.fdType == 'FNDR') &&
				 (pb.fileParam.ioFlFndrInfo.fdCreator == 'ERIK') )
			{
				found = true;
			}
		}
		++index;
	} while ( (error == noErr) && !found );
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	DTOpen(ConstStr255Param volName,
					   short vRefNum,
					   short *dtRefNum,
					   Boolean *newDTDatabase)
{
	OSErr error;
	GetVolParmsInfoBuffer volParmsInfo;
	long infoSize;
	DTPBRec pb;
	
	/* Check for volume Desktop Manager support before calling */
	infoSize = sizeof(GetVolParmsInfoBuffer);
	error = HGetVolParms(volName, vRefNum, &volParmsInfo, &infoSize);
	if ( error == noErr )
	{
		if ( hasDesktopMgr(volParmsInfo) )
		{
			pb.ioNamePtr = (StringPtr)volName;
			pb.ioVRefNum = vRefNum;
			error = PBDTOpenInform(&pb);
			/* PBDTOpenInform informs us if the desktop was just created */
			/* by leaving the low bit of ioTagInfo clear (0) */
			*newDTDatabase = ((pb.ioTagInfo & 1L) == 0);
			if ( error == paramErr )
			{
				error = PBDTGetPath(&pb);
				/* PBDTGetPath doesn't tell us if the database is new */
				/* so assume it is not new */
				*newDTDatabase = false;
			}
			*dtRefNum = pb.ioDTRefNum;
		}
		else
		{
			error = paramErr;
		}
	}
	return ( error );
}

/*****************************************************************************/

/*
**	GetAPPLFromDesktopFile
**
**	Get a application's location from the
**	Desktop file's 'APPL' resources.
*/
static	OSErr	GetAPPLFromDesktopFile(ConstStr255Param volName,
									   short vRefNum,
									   OSType creator,
									   short *applVRefNum,
									   long *applParID,
									   Str255 applName)
{
	OSErr error;
	short realVRefNum;
	Str255 desktopName;
	short savedResFile;
	short dfRefNum;
	Handle applResHandle;
	Boolean foundCreator;
	Ptr applPtr;
	long applSize;
	
	error = DetermineVRefNum(volName, vRefNum, &realVRefNum);
	if ( error == noErr )
	{
		error = GetDesktopFileName(realVRefNum, desktopName);
		if ( error == noErr )
		{
			savedResFile = CurResFile();
			/*
			**	Open the 'Desktop' file in the root directory. (because
			**	opening the resource file could preload unwanted resources,
			**	bracket the call with SetResLoad(s))
			*/
			SetResLoad(false);
			dfRefNum = HOpenResFile(realVRefNum, fsRtDirID, desktopName, fsRdPerm);
			SetResLoad(true);
			
			if ( dfRefNum != -1)
			{
				/* Get 'APPL' resource ID 0 */
				applResHandle = Get1Resource(kAPPLResType, 0);
				if ( applResHandle != NULL )
				{
					applSize = GetHandleSize((Handle)applResHandle);
					if ( applSize != 0 )	/* make sure the APPL resource isn't empty */
					{
						foundCreator = false;
						applPtr = *applResHandle;
						
						/* APPL's don't have a count so I have to use the size as the bounds */
						while ( (foundCreator == false) &&
								(applPtr < (*applResHandle + applSize)) )
						{
							if ( ((APPLRecPtr)applPtr)->creator == creator )
							{
								foundCreator = true;
							}
							else
							{
								/* fun with pointer math... */
								applPtr += sizeof(OSType) +
										   sizeof(long) +
										   ((APPLRecPtr)applPtr)->applName[0] + 1;
								/* application mappings are word aligned within the resource */
								if ( ((unsigned long)applPtr % 2) != 0 )
								{
									applPtr += 1;
								}
							}
						}
						if ( foundCreator == true )
						{
							*applVRefNum = realVRefNum;
							*applParID = ((APPLRecPtr)applPtr)->parID;
							BlockMoveData(((APPLRecPtr)applPtr)->applName,
										  applName,
										  ((APPLRecPtr)applPtr)->applName[0] + 1);
							/* error is already noErr */
						}
						else
						{
							error = afpItemNotFound;	/* didn't find a creator match */
						}
					}
					else
					{
						error = afpItemNotFound;	/* no APPL mapping available */
					}
				}
				else
				{
					error = afpItemNotFound;	/* no APPL mapping available */
				}
				
				/* restore the resource chain and close the Desktop file */
				UseResFile(savedResFile);
				CloseResFile(dfRefNum);
			}
			else
			{
				error = afpItemNotFound;
			}
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	DTXGetAPPL(ConstStr255Param volName,
						   short vRefNum,
						   OSType creator,
						   Boolean searchCatalog,
						   short *applVRefNum,
						   long *applParID,
						   Str255 applName)
{
	OSErr error;
	UniversalFMPB pb;
	short dtRefNum;
	Boolean newDTDatabase;
	short realVRefNum;
	short index;
	Boolean applFound;
	FSSpec spec;
	long actMatchCount;
	
	/* get the real vRefNum */
	error = DetermineVRefNum(volName, vRefNum, &realVRefNum);
	if ( error == noErr )
	{
		error = DTOpen(volName, vRefNum, &dtRefNum, &newDTDatabase);
		if ( error == noErr )
		{
			if ( !newDTDatabase )
			{
				index = 0;
				applFound = false;
				do
				{
					pb.dtPB.ioNamePtr = applName;
					pb.dtPB.ioDTRefNum = dtRefNum;
					pb.dtPB.ioIndex = index;
					pb.dtPB.ioFileCreator = creator;
					error = PBDTGetAPPLSync(&pb.dtPB);
					if ( error == noErr )
					{
						/* got a match - see if it is valid */
						
						*applVRefNum = realVRefNum; /* get the vRefNum now */
						*applParID = pb.dtPB.ioAPPLParID; /* get the parent ID now */
	
						/* pb.hPB.fileParam.ioNamePtr is already set */
						pb.hPB.fileParam.ioVRefNum = realVRefNum;
						pb.hPB.fileParam.ioFVersNum = 0;
						pb.hPB.fileParam.ioDirID = *applParID;
						pb.hPB.fileParam.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
						if ( PBHGetFInfoSync(&pb.hPB) == noErr )
						{
							if ( (pb.hPB.fileParam.ioFlFndrInfo.fdCreator == creator) &&
								 (pb.hPB.fileParam.ioFlFndrInfo.fdType == 'APPL') )
							{
								applFound = true;
							}
						}
					}
					++index;
				} while ( (error == noErr) && !applFound );
				if ( error != noErr )
				{
					error = afpItemNotFound;
				}
			}
			else
			{
				/* Desktop database is empty (new), set error to try CatSearch */
				error = afpItemNotFound;
			}
		}
		/* acceptable errors from Desktop Manager to continue are paramErr or afpItemNotFound */
		if ( error == paramErr )
		{
			/* if paramErr, the volume didn't support the Desktop Manager */
			/* try the Desktop file */
			
			error = GetAPPLFromDesktopFile(volName, vRefNum, creator,
											applVRefNum, applParID, applName);
			if ( error == noErr )
			{
				/* got a match - see if it is valid */
				
				pb.hPB.fileParam.ioNamePtr = applName;
				pb.hPB.fileParam.ioVRefNum = *applVRefNum;
				pb.hPB.fileParam.ioFVersNum = 0;
				pb.hPB.fileParam.ioDirID = *applParID;
				pb.hPB.fileParam.ioFDirIndex = 0;	/* use ioNamePtr and ioDirID */
				if ( PBHGetFInfoSync(&pb.hPB) == noErr )
				{
					if ( (pb.hPB.fileParam.ioFlFndrInfo.fdCreator != creator) ||
						 (pb.hPB.fileParam.ioFlFndrInfo.fdType != 'APPL') )
					{
						error = afpItemNotFound;
					}
				}
				else if ( error == fnfErr )
				{
					error = afpItemNotFound;
				}
			}
		}
		/* acceptable error from DesktopFile code to continue is afpItemNotFound */
		if ( (error == afpItemNotFound) && searchCatalog)
		{
			/* Couldn't be found in the Desktop file either, */
			/* try searching with CatSearch if requested */
			
			error = CreatorTypeFileSearch(NULL, realVRefNum, creator, kAPPLResType, &spec, 1,
											&actMatchCount, true);
			if ( (error == noErr) || (error == eofErr) )
			{
				if ( actMatchCount > 0 )
				{
					*applVRefNum = spec.vRefNum;
					*applParID = spec.parID;
					BlockMoveData(spec.name, applName, spec.name[0] + 1);
				}
				else
				{
					error = afpItemNotFound;
				}
			}
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	FSpDTXGetAPPL(ConstStr255Param volName,
							  short vRefNum,
							  OSType creator,
							  Boolean searchCatalog,
							  FSSpec *spec)
{
	return ( DTXGetAPPL(volName, vRefNum, creator, searchCatalog,
						&(spec->vRefNum), &(spec->parID), spec->name) );
}

/*****************************************************************************/

pascal	OSErr	DTGetAPPL(ConstStr255Param volName,
						  short vRefNum,
						  OSType creator,
						  short *applVRefNum,
						  long *applParID,
						  Str255 applName)
{
	/* Call DTXGetAPPL with the "searchCatalog" parameter true */ 
	return ( DTXGetAPPL(volName, vRefNum, creator, true,
						applVRefNum, applParID, applName) );
}

/*****************************************************************************/

pascal	OSErr	FSpDTGetAPPL(ConstStr255Param volName,
							 short vRefNum,
							 OSType creator,
							 FSSpec *spec)
{
	/* Call DTXGetAPPL with the "searchCatalog" parameter true */ 
	return ( DTXGetAPPL(volName, vRefNum, creator, true,
						&(spec->vRefNum), &(spec->parID), spec->name) );
}

/*****************************************************************************/

/*
**	FindBundleGivenCreator
**
**	Search the current resource file for the 'BNDL' resource with the given
**	creator and return a handle to it.
*/
static	OSErr	FindBundleGivenCreator(OSType creator,
									   BNDLRecHandle *returnBndl)
{
	OSErr			error;
	short			numOfBundles;
	short			index;
	BNDLRecHandle	theBndl;
	
	error = afpItemNotFound;	/* default to not found */
	
	/* Search each BNDL resource until we find the one with a matching creator. */
	
	numOfBundles = Count1Resources(kBNDLResType);
	index = 1;
	*returnBndl = NULL;
	
	while ( (index <= numOfBundles) && (*returnBndl == NULL) )
	{
		theBndl = (BNDLRecHandle)Get1IndResource(kBNDLResType, index);
		
		if ( theBndl != NULL )
		{
			if ( (*theBndl)->signature == creator )
			{
				/* numTypes and typeArray->count will always be the actual count minus 1, */
				/* so 0 in both fields is valid. */
				if ( ((*theBndl)->numTypes >= 0) && ((*theBndl)->typeArray->count >= 0) )
				{
					/* got it */
					*returnBndl = theBndl;
					error = noErr;
				}
			}
		}	
		
		index ++;
	}
	
	return ( error );
}

/*****************************************************************************/

/*
**	FindTypeInBundle
**
**	Given a Handle to a BNDL return a pointer to the desired type
**	in it. If the type is not found, or if the type's count < 0,
**	return afpItemNotFound.
*/
static	OSErr	FindTypeInBundle(OSType typeToFind,
								 BNDLRecHandle theBndl,
								 BundleTypePtr *returnBundleType)
{
	OSErr			error;
	short			index;
	Ptr				ptrIterator;	/* use a Ptr so we can do ugly pointer math */
	
	error = afpItemNotFound;	/* default to not found */
	
	ptrIterator = (Ptr)((*theBndl)->typeArray);
	index = 0;
	*returnBundleType = NULL;

	while ( (index < ((*theBndl)->numTypes + 1)) &&
			(*returnBundleType == NULL) )
	{
		if ( (((BundleTypePtr)ptrIterator)->type == typeToFind) &&
			 (((BundleTypePtr)ptrIterator)->count >= 0) )
		{
				*returnBundleType = (BundleTypePtr)ptrIterator;
				error = noErr;
		}
		else
		{
			ptrIterator += ( sizeof(OSType) +
							 sizeof(short) +
							 ( sizeof(IDRec) * (((BundleTypePtr)ptrIterator)->count + 1) ) );
			++index;
		}
	}
		
	return ( error );
}

/*****************************************************************************/

/*
**	GetLocalIDFromFREF
**
**	Given a pointer to a 'FREF' BundleType record, load each 'FREF' resource
**	looking for a matching fileType. If a matching fileType is found, return
**	its icon local ID. If no match is found, return afpItemNotFound as the
**	function result.
*/
static	OSErr	GetLocalIDFromFREF(BundleTypePtr theBundleType,
								   OSType fileType,
								   short *iconLocalID)
{
	OSErr			error;
	short			index;
	IDRecPtr		idIterator;
	FREFRecHandle	theFref;
	
	error = afpItemNotFound;	/* default to not found */
	
	/* For each localID in this type, get the FREF resource looking for fileType */
	index = 0;
	idIterator = &theBundleType->idArray[0];
	*iconLocalID = 0;
	
	while ( (index <= theBundleType->count) && (*iconLocalID == 0) )
	{
		theFref = (FREFRecHandle)Get1Resource(kFREFResType, idIterator->rsrcID);
		if ( theFref != NULL )
		{
			if ( (*theFref)->fileType == fileType )
			{
				*iconLocalID = (*theFref)->iconID;
				error = noErr;
			}
		}
		
		++idIterator;
		++index;
	}
	
	return ( error );
}

/*****************************************************************************/

/*
**	GetIconRsrcIDFromLocalID
**
**	Given a pointer to a 'ICN#' BundleType record, look for the IDRec with
**	the localID that matches iconLocalID. If a matching IDRec is found,
**	return the IDRec's rsrcID field value. If no match is found, return
**	afpItemNotFound as the function result.
*/
static	OSErr	GetIconRsrcIDFromLocalID(BundleTypePtr theBundleType,
										 short iconLocalID,
										 short *iconRsrcID)
{
	OSErr		error;
	short		index;
	IDRecPtr	idIterator;
	
	error = afpItemNotFound;	/* default to not found */
	
	/* Find the rsrcID of the icon family type, given the localID */
	index = 0;
	idIterator = &theBundleType->idArray[0];
	*iconRsrcID = 0;
	
	while ( (index <= theBundleType->count) && (*iconRsrcID == 0) )
	{
		if ( idIterator->localID == iconLocalID )
		{
			*iconRsrcID = idIterator->rsrcID;
			error = noErr;
		}
		
		idIterator ++;
		index ++;
	}
	
	return ( error );
}

/*****************************************************************************/

/*
**	DTIconToResIcon
**
**	Map a Desktop Manager icon type to the corresponding resource type.
**	Return (OSType)0 if there is no corresponding resource type.
*/
static	OSType	DTIconToResIcon(short iconType)
{
	OSType	resType;
	
	switch ( iconType )
	{
		case kLargeIcon:
			resType = large1BitMask;
			break;
		case kLarge4BitIcon:
			resType = large4BitData;
			break;
		case kLarge8BitIcon:
			resType = large8BitData;
			break;
		case kSmallIcon:
			resType = small1BitMask;
			break;
		case kSmall4BitIcon:
			resType = small4BitData;
			break;
		case kSmall8BitIcon:
			resType = small8BitData;
			break;
		default:
			resType = (OSType)0;
			break;
	}
	
	return ( resType );
}

/*****************************************************************************/

/*
**	GetIconFromDesktopFile
**
**	INPUT a pointer to a non-existent Handle, because we'll allocate one
**
**	search each BNDL resource for the right fileCreator and once we get it
**		find the 'FREF' type in BNDL
**		for each localID in the type, open the FREF resource
**			if the FREF is the desired fileType
**				get its icon localID
**				get the ICN# type in BNDL
**				get the icon resource number from the icon localID
**				get the icon resource type from the desktop mgr's iconType
**				get the icon of that type and number
*/
static	OSErr	GetIconFromDesktopFile(ConstStr255Param volName,
									   short vRefNum,
									   short iconType,
									   OSType fileCreator,
									   OSType fileType,
									   Handle *iconHandle)
{
	OSErr			error;
	short			realVRefNum;
	Str255			desktopName;
	short			savedResFile;
	short			dfRefNum;
	BNDLRecHandle	theBndl = NULL;
	BundleTypePtr	theBundleType;
	short			iconLocalID;
	short			iconRsrcID;
	OSType			iconRsrcType;
	Handle			returnIconHandle;	
	char			bndlState;
	
	*iconHandle = NULL;
	
	error = DetermineVRefNum(volName, vRefNum, &realVRefNum);
	if ( error == noErr )
	{
		error = GetDesktopFileName(realVRefNum, desktopName);
		if ( error == noErr )
		{
			savedResFile = CurResFile();
		
			/*
			**	Open the 'Desktop' file in the root directory. (because
			**	opening the resource file could preload unwanted resources,
			**	bracket the call with SetResLoad(s))
			*/
			SetResLoad(false);
			dfRefNum = HOpenResFile(realVRefNum, fsRtDirID, desktopName, fsRdPerm);
			SetResLoad(true);
		
			if ( dfRefNum != -1 )
			{
				/*
				**	Find the BNDL resource with the specified creator.
				*/
				error = FindBundleGivenCreator(fileCreator, &theBndl);
				if ( error == noErr )
				{
					/* Lock the BNDL resource so it won't be purged when other resources are loaded */
					bndlState = HGetState((Handle)theBndl);
					HLock((Handle)theBndl);
					
					/* Find the 'FREF' BundleType record in the BNDL resource. */
					error = FindTypeInBundle(kFREFResType, theBndl, &theBundleType);
					if ( error == noErr )
					{
						/* Find the local ID in the 'FREF' resource with the specified fileType */
						error = GetLocalIDFromFREF(theBundleType, fileType, &iconLocalID);
						if ( error == noErr )
						{
							/* Find the 'ICN#' BundleType record in the BNDL resource. */
							error = FindTypeInBundle(kIconFamResType, theBndl, &theBundleType);
							if ( error == noErr )
							{
								/* Find the icon's resource ID in the 'ICN#' BundleType record */
								error = GetIconRsrcIDFromLocalID(theBundleType, iconLocalID, &iconRsrcID);
								if ( error == noErr )
								{
									/* Map Desktop Manager icon type to resource type */
									iconRsrcType = DTIconToResIcon(iconType);
									
									if ( iconRsrcType != (OSType)0 )
									{
										/* Load the icon */
										returnIconHandle = Get1Resource(iconRsrcType, iconRsrcID);
										if ( returnIconHandle != NULL )
										{
											/* Copy the resource handle, and return the copy */
											HandToHand(&returnIconHandle);
											if ( MemError() == noErr )
											{
												*iconHandle = returnIconHandle;
											}
											else
											{
												error = afpItemNotFound;
											}
										}
										else
										{
											error = afpItemNotFound;
										}
									}
								}
							}
						}
					}
					/* Restore the state of the BNDL resource */ 
					HSetState((Handle)theBndl, bndlState);
				}
				/* Restore the resource chain and close the Desktop file */
				UseResFile(savedResFile);
				CloseResFile(dfRefNum);
			}
			else
			{
				error = ResError(); /* could not open Desktop file */
			}
		}
		if ( (error != noErr) && (error != memFullErr) )
		{
			error = afpItemNotFound;	/* force an error we should return */
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	DTGetIcon(ConstStr255Param volName,
						  short vRefNum,
						  short iconType,
						  OSType fileCreator,
						  OSType fileType,
						  Handle *iconHandle)
{
	OSErr error;
	DTPBRec pb;
	short dtRefNum;
	Boolean newDTDatabase;
	Size bufferSize;
	
	*iconHandle = NULL;
	error = DTOpen(volName, vRefNum, &dtRefNum, &newDTDatabase);
	if ( error == noErr )
	{
		/* there was a desktop database and it's now open */
		
		if ( !newDTDatabase )	/* don't bother to look in a new (empty) database */
		{
			/* get the buffer size for the requested icon type */
			switch ( iconType )
			{
				case kLargeIcon:
					bufferSize = kLargeIconSize;
					break;
				case kLarge4BitIcon:
					bufferSize = kLarge4BitIconSize;
					break;
				case kLarge8BitIcon:
					bufferSize = kLarge8BitIconSize;
					break;
				case kSmallIcon:
					bufferSize = kSmallIconSize;
					break;
				case kSmall4BitIcon:
					bufferSize = kSmall4BitIconSize;
					break;
				case kSmall8BitIcon:
					bufferSize = kSmall8BitIconSize;
					break;
				default:
					iconType = 0;
					bufferSize = 0;
					break;
			}
			if ( bufferSize != 0 )
			{
				*iconHandle = NewHandle(bufferSize);
				if ( *iconHandle != NULL )
				{
					HLock(*iconHandle);
		
					pb.ioDTRefNum = dtRefNum;
					pb.ioTagInfo = 0;
					pb.ioDTBuffer = **iconHandle;
					pb.ioDTReqCount = bufferSize;
					pb.ioIconType = iconType;
					pb.ioFileCreator = fileCreator;
					pb.ioFileType = fileType;
					error = PBDTGetIconSync(&pb);
	
					HUnlock(*iconHandle);
					
					if ( error != noErr )
					{
						DisposeHandle(*iconHandle);	/* dispose of the allocated memory */
						*iconHandle = NULL;
					}
				}
				else
				{
					error = memFullErr;	/* handle could not be allocated */
				}
			}
			else
			{
				error = paramErr;	/* unknown icon type requested */
			}
		}
		else
		{
			error = afpItemNotFound;	/* the desktop database was empty - nothing to return */
		}
	}
	else
	{
		/* There is no desktop database - try the Desktop file */
		
		error = GetIconFromDesktopFile(volName, vRefNum, iconType,
										fileCreator, fileType, iconHandle);
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	DTSetComment(short vRefNum,
							 long dirID,
							 ConstStr255Param name,
							 ConstStr255Param comment)
{
	DTPBRec pb;
	OSErr error;
	short dtRefNum;
	Boolean newDTDatabase;

	error = DTOpen(name, vRefNum, &dtRefNum, &newDTDatabase);
	if ( error == noErr )
	{
		pb.ioDTRefNum = dtRefNum;
		pb.ioNamePtr = (StringPtr)name;
		pb.ioDirID = dirID;
		pb.ioDTBuffer = (Ptr)&comment[1];
		/* Truncate the comment to 200 characters just in case */
		/* some file system doesn't range check */
		if ( comment[0] <= 200 )
		{
			pb.ioDTReqCount = comment[0];
		}
		else
		{
			pb.ioDTReqCount = 200;
		}
		error = PBDTSetCommentSync(&pb);
	}
	return (error);
}

/*****************************************************************************/

pascal	OSErr	FSpDTSetComment(const FSSpec *spec,
							  ConstStr255Param comment)
{
	return (DTSetComment(spec->vRefNum, spec->parID, spec->name, comment));
}

/*****************************************************************************/

/*
**	GetCommentID
**
**	Get the comment ID number for the Desktop file's 'FCMT' resource ID from
**	the file or folders fdComment (frComment) field.
*/
static	OSErr	GetCommentID(short vRefNum,
							 long dirID,
							 ConstStr255Param name,
							 short *commentID)
{
	CInfoPBRec pb;
	OSErr error;

	error = GetCatInfoNoName(vRefNum, dirID, name, &pb);
	*commentID = pb.hFileInfo.ioFlXFndrInfo.fdComment;
	return ( error );
}

/*****************************************************************************/

/*
**	GetCommentFromDesktopFile
**
**	Get a file or directory's Finder comment field (if any) from the
**	Desktop file's 'FCMT' resources.
*/
static	OSErr	GetCommentFromDesktopFile(short vRefNum,
										  long dirID,
										  ConstStr255Param name,
										  Str255 comment)
{
	OSErr error;
	short commentID;
	short realVRefNum;
	Str255 desktopName;
	short savedResFile;
	short dfRefNum;
	StringHandle commentHandle;
	
	/* Get the comment ID number */
	error = GetCommentID(vRefNum, dirID, name, &commentID);
	if ( error == noErr )
	{
		if ( commentID != 0 )	/* commentID == 0 means there's no comment */
		{
			error = DetermineVRefNum(name, vRefNum, &realVRefNum);
			if ( error == noErr )
			{
				error = GetDesktopFileName(realVRefNum, desktopName);
				if ( error == noErr )
				{
					savedResFile = CurResFile();
					/*
					**	Open the 'Desktop' file in the root directory. (because
					**	opening the resource file could preload unwanted resources,
					**	bracket the call with SetResLoad(s))
					*/
					SetResLoad(false);
					dfRefNum = HOpenResFile(realVRefNum, fsRtDirID, desktopName, fsRdPerm);
					SetResLoad(true);
					
					if ( dfRefNum != -1)
					{
						/* Get the comment resource */
						commentHandle = (StringHandle)Get1Resource(kFCMTResType,commentID);
						if ( commentHandle != NULL )
						{
							if ( GetHandleSize((Handle)commentHandle) > 0 )
							{
								BlockMoveData(*commentHandle, comment, *commentHandle[0] + 1);
							}
							else
							{
								error = afpItemNotFound;	/* no comment available */
							}
						}
						else
						{
							error = afpItemNotFound;	/* no comment available */
						}
						
						/* restore the resource chain and close the Desktop file */
						UseResFile(savedResFile);
						CloseResFile(dfRefNum);
					}
					else
					{
						error = afpItemNotFound;
					}
				}
				else
				{
					error = afpItemNotFound;
				}
			}
		}
		else
		{
			error = afpItemNotFound;	/* no comment available */
		}
	}
	
	return ( error );
}

/*****************************************************************************/

pascal	OSErr	DTGetComment(short vRefNum,
							 long dirID,
							 ConstStr255Param name,
							 Str255 comment)
{
	DTPBRec pb;
	OSErr error;
	short dtRefNum;
	Boolean newDTDatabase;

	if (comment != NULL)
	{
		comment[0] = 0;	/* return nothing by default */
		
		/* attempt to open the desktop database */
		error = DTOpen(name, vRefNum, &dtRefNum, &newDTDatabase);
		if ( error == noErr )
		{
			/* There was a desktop database and it's now open */
			
			if ( !newDTDatabase )
			{
				pb.ioDTRefNum = dtRefNum;
				pb.ioNamePtr = (StringPtr)name;
				pb.ioDirID = dirID;
				pb.ioDTBuffer = (Ptr)&comment[1];
				/*
				**	IMPORTANT NOTE #1: Inside Macintosh says that comments
				**	are up to 200 characters. While that may be correct for
				**	the HFS file system's Desktop Manager, other file
				**	systems (such as Apple Photo Access) return up to
				**	255 characters. Make sure the comment buffer is a Str255
				**	or you'll regret it.
				**
				**	IMPORTANT NOTE #2: Although Inside Macintosh doesn't
				**	mention it, ioDTReqCount is a input field to
				**	PBDTGetCommentSync. Some file systems (like HFS) ignore
				**	ioDTReqCount and always return the full comment --
				**	others (like AppleShare) respect ioDTReqCount and only
				**	return up to ioDTReqCount characters of the comment.
				*/
				pb.ioDTReqCount = sizeof(Str255) - 1;
				error = PBDTGetCommentSync(&pb);
				if (error == noErr)
				{
					comment[0] = (unsigned char)pb.ioDTActCount;
				}
			}
		}
		else
		{
			/* There is no desktop database - try the Desktop file */
			error = GetCommentFromDesktopFile(vRefNum, dirID, name, comment);
			if ( error != noErr )
			{
				error = afpItemNotFound;	/* return an expected error */
			}
		}
	}
	else
	{
		error = paramErr;
	}
	
	return (error);
}

/*****************************************************************************/

pascal	OSErr	FSpDTGetComment(const FSSpec *spec,
							  Str255 comment)
{
	return (DTGetComment(spec->vRefNum, spec->parID, spec->name, comment));
}

/*****************************************************************************/

pascal	OSErr	DTCopyComment(short srcVRefNum,
							  long srcDirID,
							  ConstStr255Param srcName,
							  short dstVRefNum,
							  long dstDirID,
							  ConstStr255Param dstName)
/* The destination volume must support the Desktop Manager for this to work */
{
	OSErr error;
	Str255 comment;

	error = DTGetComment(srcVRefNum, srcDirID, srcName, comment);
	if ( (error == noErr) && (comment[0] > 0) )
	{
		error = DTSetComment(dstVRefNum, dstDirID, dstName, comment);
	}
	return (error);
}

/*****************************************************************************/

pascal	OSErr	FSpDTCopyComment(const FSSpec *srcSpec,
							   const FSSpec *dstSpec)
/* The destination volume must support the Desktop Manager for this to work */
{
	return (DTCopyComment(srcSpec->vRefNum, srcSpec->parID, srcSpec->name,
						dstSpec->vRefNum, dstSpec->parID, dstSpec->name));
}

/*****************************************************************************/
