Futurebasic/Language/fsref modern

FSRef Structures-- Modern Approach

edit

The preferred way of accessing files and folders in OS X.

Description

edit

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

edit

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

edit

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

edit

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

edit

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

edit

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

edit

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