Futurebasic/Language/fsref modern
FSRef Structures-- Modern Approach
editThe preferred way of accessing files and folders in OS X.
Description
editThe 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
editHere 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
editFB 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
editFBtoC 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
editObtaining 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
editHere 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
editThe 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