/*
	File:		MoreDesktopMgr.c

	Contains:	A collection of useful high-level Desktop Manager routines.
				If the Desktop Manager is not available, use the Desktop file
				for 'read' operations.

	Version:	MoreFiles

	Copyright:	© 1992-2001 by 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.

	File Ownership:

		DRI:				Apple Macintosh Developer Technical Support

		Other Contact:		Apple Macintosh Developer Technical Support
							<http://developer.apple.com/bugreporter/>

		Technology:			DTS Sample Code

	Writers:

		(JL)	Jim Luther
		(NG)	Nitin Ganatra

	Change History (most recent first):

		 <2>	  2/7/01	JL		Added standard header. Updated names of includes. Updated
									various routines to use new calling convention of the
									MoreFilesExtras accessor functions.
		<1>		12/06/99	JL		MoreFiles 1.5.
*/

#include <MacTypes.h>
#include <MacErrors.h>
#include <MacMemory.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));
}

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