Futurebasic/Language/fsref modern

FSRef Structures-- Modern Approach
The preferred way of accessing files and folders in OS X.

Description
The OS X File Manager provides an abstraction layer that hides lower-level implementation details such as different file systems and volume formats. A key component of that abstraction layer is the FSRef.

An FSRef is an opaque reference stored in a record assigned by the File Manger to describe a file or folder. Its elements are cloaked in an 81-element array of UInt8s, the components of which are not documented by Apple. Opaque structures are accessed via Carbon Toolbox functions, rather than directly accessing individual fields of the record as in earlier APIs.

The FSRef API offers long Unicode name support, large file access, and its performance is optimized for OS X.

The contents of an FSRefs are dynamic in nature. For instance, if your code utilizes an FSRef to reference a file or folder, when the Macintosh running your code is restarted, that FSRef structure is cleared. On restart, when your code creates an FSRef to the same file or folder previously referenced, the File Manager will create a new and unique FSRef to identify that file or folder, the structure of which will differ from the former.

Here is the technical description of an FSRef as described in the Carbon Files.h header:

FSRef structure:

struct FSRef { UInt8    hidden[80];  /* private to File Manager*/ };

A discussion detailing why FSRefs are the preferred way to access files and folders, and methods to accomplish the same, is referenced in Apple's May, 2003, Technical Note TN2078.

Nuances of FSRefs which will probably have the biggest impact on your code are that an FSRef cannot represent an item which does not exist, and an FSRef does not contain the name of the item to which it refers.

Another challenge for FB users is that FB's Files$ function used to choose files and folders from a Finder dialog returns an FSSpec rather than an FSRef.

There are work-arounds for each of these challenges.

Creating FSRefs for Files with Navigation Services
Here is a modern Navigation Services replacement function for FB's Files$ function. It is compatible with both FB and FBtoC. It creates an FSRef for a file selected from the default Navigation dialog:

(Code based on this Apple example...)

include "Tlbx Navigation.incl"

_typeFSRef = _"fsrf"

local fn SelectFileFSRef( @fileRef as ^FSRef ) as OSErr dim as NavDialogCreationOptions dialogOptions dim as NavTypeListHandle    fileTypes dim as NavDialogRef     @ navRef dim as NavReplyRecord    @ navReply dim as long        @ count, @ myDummyClientData dim as FSRef          tempRef dim as OSStatus        err

err = fn NavGetDefaultDialogCreationOptions( dialogOptions ) long if ( err == _noErr ) err = fn NavCreateChooseFileDialog( @dialogOptions, fileTypes, 0, 0, 0, @myDummyClientData, navRef ) long if ( err == _noErr ) err = fn NavDialogRun( navRef ) long if ( err == _noErr ) err = fn NavDialogGetReply( navRef, navReply ) long if ( err == _noErr ) long if ( navReply.validRecord != _false ) err = fn AECountItems( navReply.selection, count ) long if ( count == 1 ) err = fn AEGetNthPtr( navReply.selection, count, _typeFSRef, #0, #0, @tempRef, SizeOf( FSRef ), #0 ) long if ( err = _noErr ) BlockMove @tempRef, fileRef, sizeof( FSRef ) end if     end if     xelse // User canceled dialog end if   end if   end if  end if                                                                                                                           end if

err = fn NavDisposeReply (navReply) call NavDialogDispose ( navRef )

end fn = err

The NavDialogCreationOptions record contains several useful fields that the user can use to customize the Navigation dialog. Among other things, the window title, dialog button names, window modality, addition of a special user message at the top of the dialog, can all be customized. These options are, for the most part, universal among a Navigation dialog whether it is used to open a single file, multiple files or a folder.

Following is an example invoking some Navigation dialog functions. These could easily be added as function input parameters, but they are shown here simply added to the function:

include "Tlbx Navigation.incl"

_typeFSRef = _"fsrf"

local fn SelectFileFSRef( fileRef as ^FSRef ) as OSErr dim as NavDialogCreationOptions dialogOptions dim as NavTypeListHandle    fileTypes dim as NavDialogRef     @ navRef dim as NavReplyRecord    @ navReply dim as long         @ count dim as FSRef          tempRef dim as Str255         s dim as OSStatus         err

err = fn NavGetDefaultDialogCreationOptions( dialogOptions ) long if ( err == _noErr )

dialogOptions.modality = _kWindowModalityAppModal

s = "SelectFileFSRef Window" dialogOptions.windowTitle = fn CFStringCreateWithCString( 0, s, _kCFStringEncodingASCII )

s = "Please choose a file..." dialogOptions.actionButtonLabel = fn CFStringCreateWithCString( 0, s, _kCFStringEncodingASCII )

s = "Here is a custom message." dialogOptions.message = fn CFStringCreateWithCString( 0, s, _kCFStringEncodingASCII )

err = fn NavCreateGetFileDialog( @dialogOptions, fileTypes, 0, 0, 0, #0, navRef ) long if ( err == _noErr ) err = fn NavDialogRun( navRef ) long if ( err == _noErr ) err = fn NavDialogGetReply( navRef, navReply ) long if ( err == _noErr ) long if ( navReply.validRecord != _false ) err = fn AECountItems( navReply.selection, count ) long if ( count == 1 ) err = fn AEGetNthPtr( navReply.selection, count, _typeFSRef, #0, #0, @tempRef, SizeOf( FSRef ), #0 ) long if ( err = _noErr ) BlockMove @tempRef, fileRef, sizeof( FSRef ) end if     end if     xelse // User canceled dialog end if   end if   end if  end if end if

err = fn NavDisposeReply (navReply) call NavDialogDispose ( navRef )

end fn = err

Creating FSRefs for Folders with Navigation Services
FB and FBtoC compatible code to return an FSRef for a folder selected from the Navigation dialog is similar to that for files. And again the dialog options may be easily customized:

include "Tlbx Navigation.incl"

_typeFSRef = _"fsrf"

local fn SelectFolderFSRef( folderRef as ^FSRef ) as OSErr dim as NavDialogCreationOptions navOptions dim as NavDialogRef     @ navRef dim as NavReplyRecord    @ navReply dim as long         @ count dim as FSRef          tempRef dim as OSStatus        err

err = fn NavGetDefaultDialogCreationOptions( navOptions ) long if ( err == _noErr ) err = fn NavCreateChooseFolderDialog( navOptions, 0, 0, #0, navRef ) long if ( err == _noErr ) err = fn NavDialogRun( navRef ) long if ( err == _noErr ) err = fn NavDialogGetReply( navRef, navReply ) long if ( err == _noErr ) long if ( navReply.validRecord != _false ) err = fn AECountItems( navReply.selection, count ) long if ( count == 1 ) err = fn AEGetNthPtr( navReply.selection, count, _typeFSRef, #0, #0, @tempRef, SizeOf( FSRef ), #0 ) long if ( err = _noErr ) BlockMove @tempRef, folderRef, sizeof( FSRef ) end if      end if      xelse // User canceled dialog end if   end if   end if  end if end if

err = fn NavDisposeReply (navReply) call NavDialogDispose ( navRef )

end fn = err

Creating an FSRef with FBtoC's Files$ Function
FBtoC offers native creation of FSRefs in its custom Files$ function:

dim as FSRef fref dim as str255 fStr

fStr = Files$( _FSRefOpen, "TEXT", "Open text file...", fsRef ) long if ( fStr[0] ) // Do something with your text file FSRef xelse // User canceled end if

Obtaining a File Name from an FSRef
Obtaining a file name from an FSRef is a bit more difficult than its predecessor, but this function should do the job:

local fn GetLongFileNameFromFSRef$( fsRef as ^FSRef ) dim as str255   @ name dim as HFSUniStr255 hsfName dim as CFStringRef  cfStr dim as OSErr     err dim as boolean    result

err = fn FSGetCatalogInfo( #fsRef, _kFSCatInfoNone, #0, hsfName, #0, #0) long if ( err == _noErr ) cfStr = fn CFStringCreateWithCharacters( 0, hsfName.unicode[0], hsfName.length ) long if ( cfStr ) result = fn CFStringGetPascalString( cfStr, @name, SizeOf( name ), _kCFStringEncodingMacRoman ) CFRelease( cfStr ) end if end if

end fn = name

Obtaining FSRef to Application Bundle
Here is a function to retrieve the FSRef to your application bundle for either a CFM or Mach-O application:

include "Tlbx Processes.Incl"

toolbox fn GetProcessBundleLocation( ProcessSerialNumber *psn, FSRef *location ) = OSStatus

local fn GetMyBundleFSRef( bundleRef as ^FSRef ) as OSErr dim as ProcessSerialNumber @ currentProcess dim as FSRef       @ tempRef, @ bundleRef dim as OSStatus       err

currentProcess.highLongOfPSN = 0 currentProcess.lowLongOfPSN = _kCurrentProcess

err = fn GetProcessBundleLocation( currentProcess, tempRef ) long if ( err == _noErr ) BlockMove @tempRef, bundleRef, SizeOf( FSRef ) end if

end fn = err

Creating a New Folder Using FSRefs
The ability to create new folders, also known as directories, is a key element in many FB programs. Following is a complete program that compiles in both FB and FBtoC. It contains error checking to prevent overwriting an existing folder.

Note: Since both FB and FBtoC headers are incomplete, this code incorporates the definitions of four Carbon Toolbox functions needed to handle its tasks.

include "Tlbx Navigation.incl" include "Tlbx MoreFilesX.incl"

_typeFSRef = _"fsrf"

// Files.h toolbox fn FSCreateDirectoryUnicode( const FSRef * parentRef,¬                   UniCharCount nameLength,¬                     const UniChar * name,¬                 FSCatalogInfoBitmap whichInfo,¬               const FSCatalogInfo * catalogInfo,¬                        FSRef * newRef,¬                       FSSpec * newSpec,¬                       UInt32 * newDirID ) = OSErr

toolbox fn FSMakeFSRefUnicode( const FSRef *parentRef,¬               UniCharCount nameLength,¬                  const UniChar *name,¬             TextEncoding textEncodingHint,¬                     FSRef *newRef ) = OSErr

// UnicodeConverter.h toolbox fn CreateTextToUnicodeInfoByEncoding( TextEncoding iEncoding,¬                TextToUnicodeInfo *oTextToUnicodeInfo ) = OSStatus

toolbox fn ConvertFromPStringToUnicode( TextToUnicodeInfo iTextToUnicodeInfo,¬                            Str255 *iPascalStr,¬                           ByteCount iOutputBufLen,¬                            ByteCount *oUnicodeLen,¬                             UniChar *oUnicodeStr ) = OSStatus

local fn CreateNewFolder( parentFolderRef as ^FSRef, newFolderName as Str255, newFolderRef as ^FSRef ) as OSErr dim as HFSUniStr255 uniName dim as OSStatus   err dim as ByteCount  @ uniLength dim as FSRef    @ tempRef

begin globals dim as TextToUnicodeInfo sTextToUnicodeInfo end globals

err = _noErr long if ( sTextToUnicodeInfo == 0 ) err = fn CreateTextToUnicodeInfoByEncoding( _kTextEncodingMacRoman, @sTextToUnicodeInfo ) long if ( err = _noErr ) err = fn ConvertFromPStringToUnicode( sTextToUnicodeInfo, @newFolderName, 510, @uniLength, @uniName.unicode[0] ) long if ( err == _noErr ) uniName.length = uniLength / sizeof( UniChar ) // Check to see if the folder already exists to avoid overwriting it... err = fn FSMakeFSRefUnicode( #parentFolderRef,¬         uniName.length, @uniName.unicode[0], _kTextEncodingUnicodeDefault, @tempRef ) long if ( err != _noErr ) err = fn FSCreateDirectoryUnicode( #parentFolderRef, uniName.length, @uniName.unicode[0], _kFSCatInfoNone, #0, #0, @tempRef, #0 ) BlockMoveData( @tempRef, newFolderRef, sizeof( FSRef) ) xelse stop "Could not create new folder. Folder already exists." exit fn       end if     xelse exit fn    end if    xelse exit fn  end if end if

end fn = err

local fn SelectFolderFSRef( folderRef as ^FSRef ) as OSErr dim as NavDialogCreationOptions navOptions dim as NavDialogRef      @ navRef dim as NavReplyRecord     @ navReply dim as long        @ count dim as FSRef          tempRef dim as OSStatus         err

err = fn NavGetDefaultDialogCreationOptions( navOptions ) long if ( err == _noErr ) err = fn NavCreateChooseFolderDialog( navOptions, 0, 0, #0, navRef ) long if ( err == _noErr ) err = fn NavDialogRun( navRef ) long if ( err == _noErr ) err = fn NavDialogGetReply( navRef, navReply ) long if ( err == _noErr ) long if ( navReply.validRecord != _false ) err = fn AECountItems( navReply.selection, count ) long if ( count == 1 ) err = fn AEGetNthPtr( navReply.selection, count, _typeFSRef, #0, #0, @tempRef, SizeOf( FSRef ), #0 ) long if ( err = _noErr ) BlockMove @tempRef, folderRef, sizeof( FSRef ) end if       end if        xelse // User canceled dialog end if     end if     end if    end if  end if

err = fn NavDisposeReply (navReply) call NavDialogDispose ( navRef )

end fn = err

dim as OSErr err dim as FSRef oldFolder, newFolder

err = fn SelectFolderFSRef( oldFolder ) long if ( err == _noErr ) err = fn CreateNewFolder( oldFolder, "Test Folder", newFolder ) end if

do HandleEvents until gFBQuit

(More discussion to follow)

4d69 646e 6967 6874

0100 0011 0110 1111 0110 0100 0110 0101 0111 0010 0010 0000 0010 0000 0010 0000