From 8aae86cf162ab842e110885072402f8be1ff75bc Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sun, 13 May 2012 17:12:21 -0400 Subject: [PATCH] Add mac FileHelpers --- src/libtomahawk/mac/FileHelpers.h | 40 +++++ src/libtomahawk/mac/FileHelpers.mm | 226 +++++++++++++++++++++++++++++ 2 files changed, 266 insertions(+) create mode 100644 src/libtomahawk/mac/FileHelpers.h create mode 100644 src/libtomahawk/mac/FileHelpers.mm diff --git a/src/libtomahawk/mac/FileHelpers.h b/src/libtomahawk/mac/FileHelpers.h new file mode 100644 index 000000000..1a25cd97d --- /dev/null +++ b/src/libtomahawk/mac/FileHelpers.h @@ -0,0 +1,40 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Leo Franchi . + */ + +#ifndef MAC_FILE_HELPERS_H +#define MAC_FILE_HELPERS_H + +#import +#import + +// Implement this delegate protocol to get notified about the result of your copy attempt +@interface NSObject (SUInstallerDelegateInformalProtocol) +- (void)moveFinished; +- (void)moveFailedWithError:(NSError *)error; +@end + +@interface FileHelpers : NSObject +{} +// Move a file from point A to point B, asking for authentication if necessary +// Will be asynchronous: Implement the delegate protocol know about the completion ++ (void) moveFile:(NSString *)source to:(NSString*)dest withDelegate:delegate; + + +@end + +#endif diff --git a/src/libtomahawk/mac/FileHelpers.mm b/src/libtomahawk/mac/FileHelpers.mm new file mode 100644 index 000000000..97ee3c4ed --- /dev/null +++ b/src/libtomahawk/mac/FileHelpers.mm @@ -0,0 +1,226 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Leo Franchi . + */ + +#import "FileHelpers.h" + +#import +#import +#import +#import +#import +#import +#import + +#include + +static NSString * const TKCopySourceKey = @"TKInstallerSourcePath"; +static NSString * const TKCopyDestinationKey = @"TKInstallerDestinationPath"; +static NSString * const TKInstallerDelegateKey = @"TKInstallerDelegate"; +static NSString * const TKInstallerResultKey = @"TKInstallerResult"; +static NSString * const TKInstallerErrorKey = @"TKInstallerError"; + +class CAutoreleasePool +{ + NSAutoreleasePool *pool; + +public: + CAutoreleasePool() + { + pool = [[NSAutoreleasePool alloc] init]; + } + + ~CAutoreleasePool() + { + [pool drain]; + } +}; + +// Authorization code based on generous contribution from Allan Odgaard. Thanks, Allan! +static BOOL AuthorizationExecuteWithPrivilegesAndWait(AuthorizationRef authorization, const char* executablePath, AuthorizationFlags options, const char* const* arguments) +{ + // *** MUST BE SAFE TO CALL ON NON-MAIN THREAD! + + sig_t oldSigChildHandler = signal(SIGCHLD, SIG_DFL); + BOOL returnValue = YES; + + if (AuthorizationExecuteWithPrivileges(authorization, executablePath, options, (char* const*)arguments, NULL) == errAuthorizationSuccess) + { + int status; + pid_t pid = wait(&status); + if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) + returnValue = NO; + } + else + returnValue = NO; + + signal(SIGCHLD, oldSigChildHandler); + return returnValue; +} + +@implementation FileHelpers + ++ (void) moveFile:(NSString *)source to:(NSString*)dest withDelegate:delegate +{ + NSLog(@"FileHelpers moving file from %@ to %@", source, dest); + + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:source, TKCopySourceKey, dest, TKCopyDestinationKey, delegate, TKInstallerDelegateKey, nil]; + [NSThread detachNewThreadSelector:@selector(performMoveWithInfo:) toTarget:self withObject:info]; +} + + ++ (void)performMoveWithInfo:(NSDictionary *)info +{ + // *** GETS CALLED ON NON-MAIN THREAD! + + CAutoreleasePool _p; + + NSString* fromPath = [info objectForKey: TKCopySourceKey]; + NSString* toPath = [info objectForKey: TKCopyDestinationKey]; + + AuthorizationRef auth = NULL; + OSStatus authStat = errAuthorizationDenied; + + NSLog(@"FileHelpers moving file from %@ to %@", fromPath, toPath); + BOOL haveOld = [[NSFileManager defaultManager] fileExistsAtPath: toPath]; + + if (haveOld == YES) { // delete the old file if it's there + if (0 != access([[toPath stringByDeletingLastPathComponent] fileSystemRepresentation], W_OK) + || 0 != access([[[toPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] fileSystemRepresentation], W_OK)) + { + const char* rmParams[] = { [toPath fileSystemRepresentation], NULL }; + // NSLog( @"WOULD DELETE: %@", [toPath fileSystemRepresentation] ); + + while( authStat == errAuthorizationDenied ) + { + authStat = AuthorizationCreate(NULL, + kAuthorizationEmptyEnvironment, + kAuthorizationFlagDefaults, + &auth); + } + if (authStat == errAuthorizationSuccess) + { + BOOL res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/rm", kAuthorizationFlagDefaults, rmParams ); + if (!res) + NSLog(@"Could not delete: %@", toPath); + } else { + qDebug() << "Failed to authenticate to delete file under target to move, aborting"; + return; + } + } else { + // We can delete it ourselves w/out authenticating + NSFileManager *manager = [[[NSFileManager alloc] init] autorelease]; + NSError* error; + BOOL success = [manager removeItemAtPath:toPath error:&error]; + + if (!success) { + NSLog(@"Failed to delete file (w/out perms) underneath copy!: %@", [[error userInfo] objectForKey: NSLocalizedDescriptionKey]); + return; + } + } + } + + FSRef dstRef, dstDirRef; + OSStatus err = FSPathMakeRefWithOptions((UInt8 *)[toPath fileSystemRepresentation], kFSPathMakeRefDoNotFollowLeafSymlink, &dstRef, NULL); + + if (err != noErr && err != fnfErr) { // If the file is not found that's fine, we're moving to there after all + qDebug() << "GOT AN ERROR DOING FSPathMakeRefWithOptions!!!!! aborting move"; + return; + } + + if (0 != access([[toPath stringByDeletingLastPathComponent] fileSystemRepresentation], W_OK) + || 0 != access([[[toPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] fileSystemRepresentation], W_OK)) + { + // Not writeable by user, so authenticate + if (!auth) { + while( authStat == errAuthorizationDenied ) + { + authStat = AuthorizationCreate(NULL, + kAuthorizationEmptyEnvironment, + kAuthorizationFlagDefaults, + &auth); + } + } + + if (authStat == errAuthorizationSuccess) + { + // Fix perms before moving so we have them correct when they arrive + struct stat dstSB; + stat([[toPath stringByDeletingLastPathComponent] fileSystemRepresentation], &dstSB); + char uidgid[42]; + snprintf(uidgid, sizeof(uidgid), "%d:%d", + dstSB.st_uid, dstSB.st_gid); + + const char* coParams[] = { "-R", uidgid, [fromPath fileSystemRepresentation], NULL }; + BOOL res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/usr/sbin/chown", kAuthorizationFlagDefaults, coParams ); + if( !res ) + qDebug() << "Failed to set permissions before moving"; + + // Do the move + const char* mvParams[] = { "-f", [fromPath fileSystemRepresentation], [toPath fileSystemRepresentation], NULL }; + res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/mv", kAuthorizationFlagDefaults, mvParams ); + if( !res ) + NSLog(@"Failed to move source file from %@ to %@ with error %@", fromPath, toPath, res ); + + AuthorizationFree(auth, 0); + auth = NULL; + } + + return; + } + + if (auth) { + AuthorizationFree(auth, 0); + auth = NULL; + } + + err = FSPathMakeRef((UInt8 *)[[toPath stringByDeletingLastPathComponent] fileSystemRepresentation], &dstDirRef, NULL); + + if (err != noErr) { + qDebug() << "GOT AN ERROR DOING FSPathMakeRef to get dir to copy into!!!!! aborting move"; + return; + } + + NSFileManager *manager = [[[NSFileManager alloc] init] autorelease]; + NSError* error; + BOOL success = [manager moveItemAtPath:fromPath toPath:toPath error:&error]; + if (!success) { + NSLog( @"Failed to do non-authenticated move! Help! %@", [[error userInfo] objectForKey: NSLocalizedDescriptionKey] ); + } + [self notifyDelegate:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:success], TKInstallerResultKey, [info objectForKey:TKInstallerDelegateKey], TKInstallerDelegateKey, error, TKInstallerErrorKey, nil]]; +} + + ++ (void)notifyDelegate:(NSDictionary *)info +{ + // *** GETS CALLED ON NON-MAIN THREAD! + BOOL result = [[info objectForKey:TKInstallerResultKey] boolValue]; + if (result) + { + if ([[info objectForKey:TKInstallerDelegateKey] respondsToSelector:@selector(moveFinished)]) + [[info objectForKey:TKInstallerDelegateKey] performSelectorOnMainThread: @selector(moveFinished) withObject:nil waitUntilDone: NO]; + } + else + { + if ([[info objectForKey:TKInstallerDelegateKey] respondsToSelector:@selector(moveFailedWithError:)]) + { + [[info objectForKey:TKInstallerDelegateKey] performSelectorOnMainThread: @selector(moveFailedWithError) withObject:[NSDictionary dictionaryWithObjectsAndKeys:[info objectForKey:TKInstallerErrorKey], TKInstallerErrorKey, nil] waitUntilDone: NO]; + } + } +} + +@end