On 2009 Jun 01, at 17:12, James Walker wrote:

Jerry Krinock wrote:
My app needs a reference to files that may or may not exist (yet).
When the file exists, I prefer to use the Alias because it tracks if the user moves it, etc. But if the file does not exist yet, my + [NSData aliasRecordFromPath:] method (shown below) returns nil. It is implied but not explicitly stated in the Alias Manager Reference that you cannot have an Alias to a nonexistent file. My main question: Is this true?

No.

Thanks, James. Indeed it appears that a "minimal" alias may be one which could not be resolved but still encapsulates a path.

Playing around with the function you recommended I discovered that it's not really needed because when FSNewAliasFromPath() returns -43 "file not found" it still returns a (minimal, I suppose) alias. So, all I needed to do was ignore the -43 error if an alias was returned!

In the other direction, however, it was like you said. FSCopyAliasInfo() returns an FSRef, which will fail if the file does not exist. So, in this case you have to detect the fnfErr and use FSCopyAliasInfo() to extract the path.

Someone replied off-list and asked why I don't use Nathan Day's NDFileAlias. Here's a link to it:

http://homepage.mac.com/nathan_day/pages/source.xml

I'm not using NDFileAlias because my old code predated it, it's a bit heavy for my purposes, and also it does not expose the Apple-defined AliasRecord data which I sometimes need for compatibility.

Finally, here's a category that works, with a little test main()...

***************** NSData+FileAlias.h **************************

#import <Cocoa/Cocoa.h>


@interface NSData (FileAlias)

/*!
 @brief    Returns the data of an alias record, given a path.

 @details  Does not require that the file specified by path exists,
 but at least its parent must exist.&nbsp;
 If file does not exist, but its parent exists, returns a minimal
 alias.&nbsp;
 If file parent does not exist, will return nil.&nbsp;
 This method may be non-deterministic.  Try it twice on the same
 path and you may get a few bits different.&nbsp;  Or, you may not.
*/
+ (NSData*)aliasRecordFromPath:(NSString*)path ;

/*!
 @brief    Returns a path specified by an alias record.

 @details  First, tries to resolve the alias and returns the resolved
 path.&nbsp;  If the file specified by the receiver does not
 exist, extracts the path and returns it.
 By convention, if the alias record specifies a directory,
 the path returned by this method will NOT have a trailing slash.
*/
- (NSString*)pathFromAliasRecord ;

@end


***************** NSData+FileAlias.m **************************

#import "NSData+FileAlias.h"

@implementation NSData (FileAlias)

+ (NSData*)aliasRecordFromPath:(NSString*)path {
    if ([path length] == 0) {
        return nil ;
    }

    const char* pathC = [path UTF8String] ;

    OSErr osErr ;
    AliasHandle aliasHandle = NULL ;
    osErr = FSNewAliasFromPath (
                                NULL,
                                pathC,
                                0,
                                &aliasHandle,
                                NULL
                                ) ;

    NSData* data = nil ;
    if (
        (osErr == noErr)
        // ... File exists and we have a full alias
        ||
        ((osErr == fnfErr) && (aliasHandle != NULL))
        // ... File does not exist and we have a minimal alias
        ) {

#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
        Size size = GetAliasSize(aliasHandle) ;
#else
        Size size = aliasHeader.aliasSize ;
#endif

        AliasPtr aliasPtr = *aliasHandle ;
        data = [NSData dataWithBytes:aliasPtr
                              length:size] ;
    }

    return data ;
}

- (NSString*)pathFromAliasRecord {
    unsigned short nBytesAliasRecord ;
    /*
     In Aliases.h, note that the AliasRecord struct is opaque if
     MAC_OS_X_MIN_VERSION_REQUIRED >= MAC_OS_X_VERSION_10_4.  In other
words, if the "Mac OS X Deployment Target" setting for your project is
     10.4 or later, the AliasRecord struct is opaque.

That's because AliasRecords, as you've noticed, get written to disk but are also referenced in data, which means that they often have to be big-endian even on little-endian systems. Rather than enumerate the cases in which you'd want big- or little-endian AliasRecords, we made the data type opaque and added new APIs which deal in native- endian data. They're Get/SetAliasUserType and GetAliasSize, and there are also FromPtr versions of each if you have an AliasRecord * instead of
     an AliasHandle.

     Eric Albert, Apple */

    // Cast to an AliasRecord and resolve the alias.
    AliasRecord aliasHeader = *((AliasPtr)[self bytes]) ;
    /*
     Here is how MAC_OS_X_VERSION_MIN_REQUIRED gets The gcc driver sets
_ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED_ based on MACOSX_DEPLOYMENT_TARGET
     environment variable, which is defined in target Build Settings.
     Then, AvailabilityMacros.h will set MAC_OS_X_VERSION_MIN_REQUIRED
     based on _ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED_
     */
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
    AliasPtr ap = &aliasHeader ;
    AliasHandle ah = &ap ;
    nBytesAliasRecord = GetAliasSize(ah) ;
#else
    nBytesAliasRecord = aliasHeader.aliasSize ;
#endif

    Handle handle = NULL;
    FSRef resolvedFSRef;
    NSString* path = nil ;
    int err = 0 ;

    // Move the now-decoded data into the Handle.
    if (PtrToHand([self bytes], &handle, nBytesAliasRecord) != noErr) {
NSLog(@"Internal Error 589-5451. Can't allocate handle for alias") ;
        err = 1 ;
    }

    if (err == 0) {
        // Successfully created the handle

        // Now try and resolve the alias
        Boolean changed ;
        OSErr osErr = FSResolveAlias(NULL,
                                     (AliasHandle)handle,
                                     &resolvedFSRef,
                                     &changed) ;

        if (osErr == noErr) {
            // Alias was resolved.  Now get its path from resolvedFSRef
            char pathC[4096] ;
            OSStatus osStatus = FSRefMakePath(
                                              &resolvedFSRef,
                                              (UInt8*)pathC,
                                              sizeof(pathC)
            ) ;

            if (osStatus != noErr) {
NSLog(@"Internal Error 959-2697. OSStatus %i from FSResolveAlias", osStatus) ;
                err = 1 ;
            }
            else {
                path = [NSString stringWithCString:pathC] ;

// The full path returned by FSRefMakePath will NOT have a trailing slash UNLESS // the path is the root, i.e. @"/". In that case it will. Thus, in order to return // a standard result to which "/Filename.ext" should be appended, we remove that:
                if ([path length] == 1)
                    path = @"" ;
            }
        }
        else if (osErr == fnfErr) {
// The alias could not be resolved because the file does not exist. // However, we can still extract the expected path from the alias.
            osErr = FSCopyAliasInfo (
                                     (AliasHandle)handle,
                                     NULL,
                                     NULL,
                                     (CFStringRef*)&(path),
                                     NULL,
                                     NULL
                                     ) ;

// There may be a bug in the above function. If the alias is to // that of a nonexistent directory in the root, for example,
            //    /Yousers
            // Then the path returned will begin with two slashes.
            // To work around that,
            if ([path hasPrefix:@"//"]) {
                path = [path substringFromIndex:1] ;
            }
        }
        else {
            // File could not be found.  The invoker is responsible to
// raise an error or exception if desired when nil is returned.
            err = 1 ;
        }
    }

    if (handle)
        DisposeHandle(handle);

    return path ;
}

@end


***************** Test Project **************************

#import "NSData+FileAlias.h"

void TestPath(NSString* path) {
    NSData* alias = [NSData aliasRecordFromPath:path] ;
    NSString* recoveredPath = [alias pathFromAliasRecord] ;

    if ([path isEqualToString:recoveredPath]) {
        NSLog(@"Passed: %@", path) ;
    }
    else {
        NSLog(@"Failed: %@", path) ;
        NSLog(@"   Got: %@", recoveredPath) ;
    }

}



int main(int argc, const char *argv[]) {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init] ;

    NSLog(@"*** Tests which should pass ***") ;
    TestPath(@"/Users") ;
    TestPath(@"/Users/Ptolemy") ;
    TestPath(@"/Yousers") ;
    TestPath(@"/Volumes/NoSuchVolume") ;
    TestPath(@"/Users/NoSuchFileButParentExists") ;
TestPath([NSHomeDirectory() stringByAppendingPathComponent:@".DS_Store"]) ;
    TestPath(@"/Applications/Safari.app/Contents/MacOS/Safari") ;

    NSLog(@"\n") ;
    NSLog(@"\n") ;

    NSLog(@"*** Tests which should fail ***") ;
    TestPath(@"/Users/") ;
    TestPath(@"/Yousers/") ;
    TestPath(@"/Yousers/Ptolemy") ;
    TestPath(@"") ;
    TestPath(@"/") ;
    TestPath(@"/No/Such/File/And/Parent/Does/Not/Exist") ;
    TestPath(@"NotEvenAPath") ;

    [pool release] ;

    return 0 ;
}


_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to