Re: Referring to file by Alias ^or^ path
Re: Referring to file by Alias ^or^ path
- Subject: Re: Referring to file by Alias ^or^ path
- From: Jerry Krinock <email@hidden>
- Date: Tue, 2 Jun 2009 14:50:49 -0700
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.
If file does not exist, but its parent exists, returns a minimal
alias.
If file parent does not exist, will return nil.
This method may be non-deterministic. Try it twice on the same
path and you may get a few bits different. 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. 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 (email@hidden)
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:
This email sent to email@hidden