/*
**	Apple Macintosh Developer Technical Support
**
**	Routines for dealing with full pathnames... if you really must.
**
**	by Jim Luther, Apple Developer Technical Support Emeritus
**
**	File:		FullPath.c
**
**	Copyright © 1995-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 <MacTypes.h>
#include <MacErrors.h>
#include <MacMemory.h>
#include <Files.h>
#include <TextUtils.h>

#include "ut_debugmsg.h"
#include "ut_assert.h"
#include "ut_MacFiles.h"


/*!
	Return a UNIX Path from a FSRef
	\param fileRef the FSRef
	\return the path in UTF-8. NULL in case of error. The caller must free() the return pointer.
 */
char * UT_FSRefToUNIXPath (const FSRef * fileRef)
{
	char * result;
	CFIndex len;
	CFURLRef   destURL = ::CFURLCreateFromFSRef (kCFAllocatorDefault, fileRef);
	CFStringRef   destPath = ::CFURLCopyFileSystemPath (destURL, kCFURLPOSIXPathStyle);
	len = CFStringGetMaximumSizeForEncoding (CFStringGetLength (destPath), kCFStringEncodingUTF8) + 1;
	result = (char *)malloc (len * sizeof (char));
	bool ok = CFStringGetCString (destPath, result, len, kCFStringEncodingUTF8);
	UT_ASSERT (ok);
	
	CFRelease (destPath);
	CFRelease (destURL);
	return result;
}


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

OSErr	FSMakeFSSpecCompat(short vRefNum,
								   long dirID,
								   ConstStr255Param fileName,
								   FSSpec *spec)
{
	OSErr	result;
	
	/* Let the file system create the FSSpec if it can since it does the job */
	/* much more efficiently than I can. */
	result = FSMakeFSSpec(vRefNum, dirID, fileName, spec);
	/* Fix a bug in Macintosh PC Exchange's MakeFSSpec code where 0 is */
	/* returned in the parID field when making an FSSpec to the volume's */
	/* root directory by passing a full pathname in MakeFSSpec's */
	/* fileName parameter. */
	if ( (result == noErr) && (spec->parID == 0) )
		spec->parID = fsRtParID;
	return ( result );
}

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

OSErr	FSpGetFullPath(const FSSpec *spec,
							   short *fullPathLength,
							   Handle *fullPath)
{
	OSErr		result;
	OSErr		realResult;
	FSSpec		tempSpec;
	CInfoPBRec	pb;
	
	*fullPathLength = 0;
	*fullPath = NULL;
	
	
	/* Default to noErr */
	realResult = result = noErr;
	
	/* work around Nav Services "bug" (it returns invalid FSSpecs with empty names) */
	if ( spec->name[0] == 0 )
	{
		result = FSMakeFSSpecCompat(spec->vRefNum, spec->parID, spec->name, &tempSpec);
	}
	else
	{
		/* Make a copy of the input FSSpec that can be modified */
		BlockMoveData(spec, &tempSpec, sizeof(FSSpec));
	}
	
	if ( result == noErr )
	{
		if ( tempSpec.parID == fsRtParID )
		{
			/* The object is a volume */
			
			/* Add a colon to make it a full pathname */
			++tempSpec.name[0];
			tempSpec.name[tempSpec.name[0]] = ':';
			
			/* We're done */
			result = PtrToHand(&tempSpec.name[1], fullPath, tempSpec.name[0]);
		}
		else
		{
			/* The object isn't a volume */
			
			/* Is the object a file or a directory? */
			pb.dirInfo.ioNamePtr = tempSpec.name;
			pb.dirInfo.ioVRefNum = tempSpec.vRefNum;
			pb.dirInfo.ioDrDirID = tempSpec.parID;
			pb.dirInfo.ioFDirIndex = 0;
			result = PBGetCatInfoSync(&pb);
			// Allow file/directory name at end of path to not exist.
			realResult = result;
			if ( (result == noErr) || (result == fnfErr) )
			{
				/* if the object is a directory, append a colon so full pathname ends with colon */
				if ( (result == noErr) && (pb.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0 )
				{
					++tempSpec.name[0];
					tempSpec.name[tempSpec.name[0]] = ':';
				}
				
				/* Put the object name in first */
				result = PtrToHand(&tempSpec.name[1], fullPath, tempSpec.name[0]);
				if ( result == noErr )
				{
					/* Get the ancestor directory names */
					pb.dirInfo.ioNamePtr = tempSpec.name;
					pb.dirInfo.ioVRefNum = tempSpec.vRefNum;
					pb.dirInfo.ioDrParID = tempSpec.parID;
					do	/* loop until we have an error or find the root directory */
					{
						pb.dirInfo.ioFDirIndex = -1;
						pb.dirInfo.ioDrDirID = pb.dirInfo.ioDrParID;
						result = PBGetCatInfoSync(&pb);
						if ( result == noErr )
						{
							/* Append colon to directory name */
							++tempSpec.name[0];
							tempSpec.name[tempSpec.name[0]] = ':';
							
							/* Add directory name to beginning of fullPath */
							(void) Munger(*fullPath, 0, NULL, 0, &tempSpec.name[1], tempSpec.name[0]);
							result = MemError();
						}
					} while ( (result == noErr) && (pb.dirInfo.ioDrDirID != fsRtDirID) );
				}
			}
		}
	}
	
	if ( result == noErr )
	{
		/* Return the length */
		*fullPathLength = GetHandleSize(*fullPath);
		result = realResult;	// return realResult in case it was fnfErr
	}
	else
	{
		/* Dispose of the handle and return NULL and zero length */
		if ( *fullPath != NULL )
		{
			DisposeHandle(*fullPath);
		}
		*fullPath = NULL;
		*fullPathLength = 0;
	}
	
	return ( result );
}

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