Skip to content
Browse files

Resolve any aliases when finding the Applications directory

  • Loading branch information...
1 parent d4df328 commit 7255eaa9e88dd78a70f87d25e550a46e5735f9bd Andy Kim committed
Showing with 279 additions and 4 deletions.
  1. +6 −0 LetsMove.xcodeproj/project.pbxproj
  2. +28 −0 NSString+SymlinksAndAliases.h
  3. +237 −0 NSString+SymlinksAndAliases.m
  4. +3 −2 PFMoveApplication.m
  5. +5 −2 README.md
View
6 LetsMove.xcodeproj/project.pbxproj
@@ -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 */
@@ -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>"; };
@@ -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>";
@@ -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;
};
View
28 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
View
237 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
View
5 PFMoveApplication.m
@@ -16,6 +16,7 @@
//
#import "PFMoveApplication.h"
+#import "NSString+SymlinksAndAliases.h"
#import <Security/Security.h>
// Strings
@@ -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) {
View
7 README.md
@@ -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.
@@ -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:
@@ -50,7 +53,7 @@ Code Contributors:
* Kevin LaCoste
* Rasmus Andersson
* Timothy J. Wood
-
+* Matt Gallagher (NSString+SymlinksAndAliases)
Translators:
------------

0 comments on commit 7255eaa

Please sign in to comment.
Something went wrong with that request. Please try again.