Adventures in the transition from C to Cocoa.

Monday, August 6, 2007

Anti-aliases

Coming from a Linux background, I'm fairly comfortable with the idea of Symbolic Links. These are kind of like shortcuts on steroids; they transparently pose as files residing elsewhere, allowing all kinds of power (and problems).

Since OS X has some pretty strong Unix underpinnings, it came as no surprise to find that it supports Symbolic links out of the box. Unfortunately, the only way to create them is still with Terminal.app. No problem for me, being a keyboard cowboy, but for the average user it's a huge inconvenience.

Finder has a provision for making shortcuts, called aliases. This, unlike symbolic linking, is quite easy to use, and many non-programmer users use alias functionality. In fact, I even used aliases for a while, thinking they were simply symlinks renamed.

Then, along came a bug report to the Folder Movies Patch at kineme.net. Apparently, aliases were not symbolic links.

After a considerable amount of research, I discovered that aliases are Basically regular files. They report their length as zero bytes, and they store their data in a resource fork (oh how I loathe this concept...) To view the data, you can open a terminal, and type cat [alias file]/rsrc. You'll be greeted with some binary garbage, and some plain-text parts of the path to the real file.

So, how do we get a program to handle these peculiar files? Through some actions known as "Alias Resolution."

I won't bore you with all the exploration I did to come to the result, but here it is:


/* Multiple aliases in a path won't resolve here (we need to handle them one at a time)
so we need to prune off parts of the path recursively and try resolving those, then
rebuild the pruned off parts onto the resolved alias
*/
+ (NSString*) resolveAlias:(NSString *)filePath isFolder:(Boolean*)folder
{
unsigned char pathBuffer[4096];
FSRef fsRef;

if( FSPathMakeRef((const UInt8*)[filePath UTF8String], &fsRef,NO) == noErr )
{
Boolean isAlias = FALSE;
if( FSResolveAliasFile(&fsRef, TRUE, folder, &isAlias) == noErr && isAlias)
{
FSRefMakePath(&fsRef, pathBuffer, 4096);
return [NSString stringWithUTF8String:(const char*)pathBuffer];
}
}
else
{
//NSLog(@"FSPathMakeRef failed for %@\n",filePath);
// this fails when mutliple alises are in one path, so we prune and rebuild here
return [NSString stringWithFormat:@"%@/%@",
[self resolveAlias:[filePath stringByDeletingLastPathComponent] isFolder:folder],
[filePath lastPathComponent]
];
}
return nil;
}


This recursive method will take an NSString path, and resolve aliases as it goes. This means you can have an alias inside another alias, and they both get resolved correctly. It also takes a Boolean pointer to let you know if the ultimate target is a folder or not. It returns nil if there aren't any aliases in the path to resolve, otherwise it returns the resolved path, allowing you to access the target.

Categories