Futurebasic/Language/fsref modern

From Wikibooks, open books for an open world
< Futurebasic‎ | Language(Redirected from Futurebasic/language/fsref modern)
Jump to: navigation, search

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 it's 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