Skip to content

Commit

Permalink
Resolve any aliases when finding the Applications directory
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy Kim committed May 2, 2011
1 parent d4df328 commit 7255eaa
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 4 deletions.
6 changes: 6 additions & 0 deletions LetsMove.xcodeproj/project.pbxproj
Expand Up @@ -15,6 +15,7 @@
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
A10D5AD3106C082200E53420 /* MoveApplication.strings in Resources */ = {isa = PBXBuildFile; fileRef = A10D5AD1106C082200E53420 /* MoveApplication.strings */; };
A120E7F810630B160045BE3F /* PFMoveApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = A120E7F710630B160045BE3F /* PFMoveApplication.m */; };
A12AF6B1136E7D0100F0BC7D /* NSString+SymlinksAndAliases.m in Sources */ = {isa = PBXBuildFile; fileRef = A12AF6B0136E7D0100F0BC7D /* NSString+SymlinksAndAliases.m */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -39,6 +40,8 @@
A10D5BFB106C550F00E53420 /* Japanese */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = Japanese; path = Japanese.lproj/MoveApplication.strings; sourceTree = "<group>"; };
A120E7F610630B160045BE3F /* PFMoveApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFMoveApplication.h; sourceTree = "<group>"; };
A120E7F710630B160045BE3F /* PFMoveApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFMoveApplication.m; sourceTree = "<group>"; };
A12AF6AF136E7D0100F0BC7D /* NSString+SymlinksAndAliases.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SymlinksAndAliases.h"; sourceTree = "<group>"; };
A12AF6B0136E7D0100F0BC7D /* NSString+SymlinksAndAliases.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SymlinksAndAliases.m"; sourceTree = "<group>"; };
A13A579A119960E500686A9E /* Norwegian */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = Norwegian; path = Norwegian.lproj/MoveApplication.strings; sourceTree = "<group>"; };
A1A4C5A8106760B100AF3142 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = "<group>"; };
A1B3A84D11290C9600A72428 /* Danish */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = Danish; path = Danish.lproj/MoveApplication.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -66,6 +69,8 @@
256AC3D90F4B6AC300CF3369 /* LetsMoveAppDelegate.m */,
A120E7F610630B160045BE3F /* PFMoveApplication.h */,
A120E7F710630B160045BE3F /* PFMoveApplication.m */,
A12AF6AF136E7D0100F0BC7D /* NSString+SymlinksAndAliases.h */,
A12AF6B0136E7D0100F0BC7D /* NSString+SymlinksAndAliases.m */,
);
name = Classes;
sourceTree = "<group>";
Expand Down Expand Up @@ -211,6 +216,7 @@
8D11072D0486CEB800E47090 /* main.m in Sources */,
256AC3DA0F4B6AC300CF3369 /* LetsMoveAppDelegate.m in Sources */,
A120E7F810630B160045BE3F /* PFMoveApplication.m in Sources */,
A12AF6B1136E7D0100F0BC7D /* NSString+SymlinksAndAliases.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
28 changes: 28 additions & 0 deletions NSString+SymlinksAndAliases.h
@@ -0,0 +1,28 @@
//
// NSString+SymlinksAndAliases.h
// ResolvePath
//
// Created by Matt Gallagher on 2010/02/22.
// Copyright 2010 Matt Gallagher. All rights reserved.
//
// Permission is given to use this source code file, free of charge, in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.
//

#import <Cocoa/Cocoa.h>

@interface NSString (SymlinksAndAliases)

- (NSString *)stringByResolvingSymlinksAndAliases;
- (NSString *)stringByIterativelyResolvingSymlinkOrAlias;

- (NSString *)stringByResolvingSymlink;
- (NSString *)stringByConditionallyResolvingSymlink;

- (NSString *)stringByResolvingAlias;
- (NSString *)stringByConditionallyResolvingAlias;

@end
237 changes: 237 additions & 0 deletions NSString+SymlinksAndAliases.m
@@ -0,0 +1,237 @@
//
// NSString+SymlinksAndAliases.m
// ResolvePath
//
// Created by Matt Gallagher on 2010/02/22.
// Copyright 2010 Matt Gallagher. All rights reserved.
//
// Permission is given to use this source code file, free of charge, in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.
//

#import "NSString+SymlinksAndAliases.h"
#include <sys/stat.h>

@implementation NSString (SymlinksAndAliases)

//
// stringByResolvingSymlinksAndAliases
//
// Tries to make a standardized, absolute path from the current string,
// resolving any aliases or symlinks in the path.
//
// returns the fully resolved path (if possible) or nil (if resolution fails)
//
- (NSString *)stringByResolvingSymlinksAndAliases
{
//
// Convert to a standardized absolute path.
//
NSString *path = [self stringByStandardizingPath];
if (![path hasPrefix:@"/"])
{
return nil;
}

//
// Break into components. First component ("/") needs no resolution, so
// we only need to handle subsequent components.
//
NSArray *pathComponents = [path pathComponents];
NSString *resolvedPath = [pathComponents objectAtIndex:0];
pathComponents = [pathComponents
subarrayWithRange:NSMakeRange(1, [pathComponents count] - 1)];

//
// Process all remaining components.
//
for (NSString *component in pathComponents)
{
resolvedPath = [resolvedPath stringByAppendingPathComponent:component];
resolvedPath = [resolvedPath stringByIterativelyResolvingSymlinkOrAlias];
if (!resolvedPath)
{
return nil;
}
}

return resolvedPath;
}

//
// stringByIterativelyResolvingSymlinkOrAlias
//
// Resolves the path where the final component could be a symlink and any
// component could be an alias.
//
// returns the resolved path
//
- (NSString *)stringByIterativelyResolvingSymlinkOrAlias
{
NSString *path = self;
NSString *aliasTarget = nil;
struct stat fileInfo;

//
// Use lstat to determine if the file is a symlink
//
if (lstat([[NSFileManager defaultManager]
fileSystemRepresentationWithPath:path], &fileInfo) < 0)
{
return nil;
}

//
// While the file is a symlink or we can resolve aliases in the path,
// keep resolving.
//
while (S_ISLNK(fileInfo.st_mode) ||
(!S_ISDIR(fileInfo.st_mode) &&
(aliasTarget = [path stringByConditionallyResolvingAlias]) != nil))
{
if (S_ISLNK(fileInfo.st_mode))
{
//
// Resolve the symlink final component in the path
//
NSString *symlinkPath = [path stringByConditionallyResolvingSymlink];
if (!symlinkPath)
{
return nil;
}
path = symlinkPath;
}
else
{
path = aliasTarget;
}

//
// Use lstat to determine if the file is a symlink
//
if (lstat([[NSFileManager defaultManager]
fileSystemRepresentationWithPath:path], &fileInfo) < 0)
{
path = nil;
continue;
}
}

return path;
}

//
// stringByResolvingAlias
//
// Attempts to resolve the single alias at the end of the path.
//
// returns the resolved alias or self if path wasn't an alias or couldn't be
// resolved.
//
- (NSString *)stringByResolvingAlias
{
NSString *aliasTarget = [self stringByConditionallyResolvingAlias];
if (aliasTarget)
{
return aliasTarget;
}
return self;
}

//
// stringByResolvingSymlink
//
// Attempts to resolve the single symlink at the end of the path.
//
// returns the resolved path or self if path wasn't a symlink or couldn't be
// resolved.
//
- (NSString *)stringByResolvingSymlink
{
NSString *symlinkTarget = [self stringByConditionallyResolvingSymlink];
if (symlinkTarget)
{
return symlinkTarget;
}
return self;
}

//
// stringByConditionallyResolvingSymlink
//
// Attempt to resolve the symlink pointed to by the path.
//
// returns the resolved path (if it was a symlink and resolution is possible)
// otherwise nil
//
- (NSString *)stringByConditionallyResolvingSymlink
{
//
// Resolve the symlink final component in the path
//
NSString *symlinkPath =
[[NSFileManager defaultManager]
destinationOfSymbolicLinkAtPath:self
error:NULL];
if (!symlinkPath)
{
return nil;
}
if (![symlinkPath hasPrefix:@"/"])
{
//
// For relative path symlinks (common case), remove the
// relative links
//
symlinkPath =
[[self stringByDeletingLastPathComponent]
stringByAppendingPathComponent:symlinkPath];
symlinkPath = [symlinkPath stringByStandardizingPath];
}
return symlinkPath;
}

//
// stringByConditionallyResolvingAlias
//
// Attempt to resolve the alias pointed to by the path.
//
// returns the resolved path (if it was an alias and resolution is possible)
// otherwise nil
//
- (NSString *)stringByConditionallyResolvingAlias
{
NSString *resolvedPath = nil;

CFURLRef url = CFURLCreateWithFileSystemPath
(kCFAllocatorDefault, (CFStringRef)self, kCFURLPOSIXPathStyle, NO);
if (url != NULL)
{
FSRef fsRef;
if (CFURLGetFSRef(url, &fsRef))
{
Boolean targetIsFolder, wasAliased;
OSErr err = FSResolveAliasFileWithMountFlags(
&fsRef, false, &targetIsFolder, &wasAliased, kResolveAliasFileNoUI);
if ((err == noErr) && wasAliased)
{
CFURLRef resolvedUrl = CFURLCreateFromFSRef(kCFAllocatorDefault, &fsRef);
if (resolvedUrl != NULL)
{
resolvedPath =
[(id)CFURLCopyFileSystemPath(resolvedUrl, kCFURLPOSIXPathStyle)
autorelease];
CFRelease(resolvedUrl);
}
}
}
CFRelease(url);
}

return resolvedPath;
}

@end
5 changes: 3 additions & 2 deletions PFMoveApplication.m
Expand Up @@ -16,6 +16,7 @@
//

#import "PFMoveApplication.h"
#import "NSString+SymlinksAndAliases.h"
#import <Security/Security.h>

// Strings
Expand Down Expand Up @@ -268,13 +269,13 @@ void PFMoveToApplicationsFolderIfNecessary() {

if ([fm fileExistsAtPath:userApplicationsDir isDirectory:&isDirectory] && isDirectory) {
if (isUserDirectory) *isUserDirectory = YES;
return userApplicationsDir;
return [userApplicationsDir stringByResolvingSymlinksAndAliases];
}
}

// No user Applications directory. Return the machine local Applications directory
if (isUserDirectory) *isUserDirectory = NO;
return [NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSLocalDomainMask, YES) lastObject];
return [[NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSLocalDomainMask, YES) lastObject] stringByResolvingSymlinksAndAliases];
}

static BOOL IsInApplicationsFolder(NSString *path) {
Expand Down
7 changes: 5 additions & 2 deletions README.md
Expand Up @@ -7,6 +7,9 @@ A sample project that demonstrates how to move a running Mac OS X application to
Version History
---------------

* 1.6
- Resolve any aliases when finding the Applications directory

* 1.5.2
- Cleaned up the code a bit. Almost functionally equivalent to 1.5.1.

Expand Down Expand Up @@ -39,7 +42,7 @@ Version History

Requirements
------------
Builds and runs on Mac OS X 10.4 or higher
Builds and runs on Mac OS X 10.4 or higher.


Code Contributors:
Expand All @@ -50,7 +53,7 @@ Code Contributors:
* Kevin LaCoste
* Rasmus Andersson
* Timothy J. Wood

* Matt Gallagher (NSString+SymlinksAndAliases)

Translators:
------------
Expand Down

0 comments on commit 7255eaa

Please sign in to comment.