cog/Frameworks/Sparkle/SUInstaller.m

183 lines
5.9 KiB
Objective-C

//
// SUInstaller.m
// Sparkle
//
// Created by Andy Matuschak on 4/10/08.
// Copyright 2008 Andy Matuschak. All rights reserved.
//
#import "SUInstaller.h"
#import "SUPlainInstaller.h"
#import "SUPackageInstaller.h"
#import "SUHost.h"
#import "SUConstants.h"
#import "SULog.h"
@implementation SUInstaller
static NSString* sUpdateFolder = nil;
+(NSString*) updateFolder
{
return sUpdateFolder;
}
+ (BOOL)isAliasFolderAtPath:(NSString *)path
{
FSRef fileRef;
OSStatus err = noErr;
Boolean aliasFileFlag, folderFlag;
NSURL *fileURL = [NSURL fileURLWithPath:path];
if (FALSE == CFURLGetFSRef((CFURLRef)fileURL, &fileRef))
err = coreFoundationUnknownErr;
if (noErr == err)
err = FSIsAliasFile(&fileRef, &aliasFileFlag, &folderFlag);
if (noErr == err)
return (BOOL)(aliasFileFlag && folderFlag);
else
return NO;
}
+ (NSString *)installSourcePathInUpdateFolder:(NSString *)inUpdateFolder forHost:(SUHost *)host isPackage:(BOOL *)isPackagePtr
{
// Search subdirectories for the application
NSString *currentFile,
*newAppDownloadPath = nil,
*bundleFileName = [[host bundlePath] lastPathComponent],
*alternateBundleFileName = [[host name] stringByAppendingPathExtension:[[host bundlePath] pathExtension]];
BOOL isPackage = NO;
NSString *fallbackPackagePath = nil;
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath: inUpdateFolder];
[sUpdateFolder release];
sUpdateFolder = [inUpdateFolder retain];
while ((currentFile = [dirEnum nextObject]))
{
NSString *currentPath = [inUpdateFolder stringByAppendingPathComponent:currentFile];
if ([[currentFile lastPathComponent] isEqualToString:bundleFileName] ||
[[currentFile lastPathComponent] isEqualToString:alternateBundleFileName]) // We found one!
{
isPackage = NO;
newAppDownloadPath = currentPath;
break;
}
else if ([[currentFile pathExtension] isEqualToString:@"pkg"] ||
[[currentFile pathExtension] isEqualToString:@"mpkg"])
{
if ([[[currentFile lastPathComponent] stringByDeletingPathExtension] isEqualToString:[bundleFileName stringByDeletingPathExtension]])
{
isPackage = YES;
newAppDownloadPath = currentPath;
break;
}
else
{
// Remember any other non-matching packages we have seen should we need to use one of them as a fallback.
fallbackPackagePath = currentPath;
}
}
else
{
// Try matching on bundle identifiers in case the user has changed the name of the host app
NSBundle *incomingBundle = [NSBundle bundleWithPath:currentPath];
if(incomingBundle && [[incomingBundle bundleIdentifier] isEqualToString:[[host bundle] bundleIdentifier]])
{
isPackage = NO;
newAppDownloadPath = currentPath;
break;
}
}
// Some DMGs have symlinks into /Applications! That's no good!
if ([self isAliasFolderAtPath:currentPath])
[dirEnum skipDescendents];
}
// We don't have a valid path. Try to use the fallback package.
if (newAppDownloadPath == nil && fallbackPackagePath != nil)
{
isPackage = YES;
newAppDownloadPath = fallbackPackagePath;
}
if (isPackagePtr) *isPackagePtr = isPackage;
return newAppDownloadPath;
}
+ (NSString *)appPathInUpdateFolder:(NSString *)updateFolder forHost:(SUHost *)host
{
BOOL isPackage = NO;
NSString *path = [self installSourcePathInUpdateFolder:updateFolder forHost:host isPackage:&isPackage];
return isPackage ? nil : path;
}
+ (void)installFromUpdateFolder:(NSString *)inUpdateFolder overHost:(SUHost *)host installationPath:(NSString *)installationPath delegate:delegate synchronously:(BOOL)synchronously versionComparator:(id <SUVersionComparison>)comparator
{
BOOL isPackage = NO;
NSString *newAppDownloadPath = [self installSourcePathInUpdateFolder:inUpdateFolder forHost:host isPackage:&isPackage];
if (newAppDownloadPath == nil)
{
[self finishInstallationToPath:installationPath withResult:NO host:host error:[NSError errorWithDomain:SUSparkleErrorDomain code:SUMissingUpdateError userInfo:[NSDictionary dictionaryWithObject:@"Couldn't find an appropriate update in the downloaded package." forKey:NSLocalizedDescriptionKey]] delegate:delegate];
}
else
{
[(isPackage ? [SUPackageInstaller class] : [SUPlainInstaller class]) performInstallationToPath:installationPath fromPath:newAppDownloadPath host:host delegate:delegate synchronously:synchronously versionComparator:comparator];
}
}
+ (void)mdimportInstallationPath:(NSString *)installationPath
{
// *** GETS CALLED ON NON-MAIN THREAD!
SULog( @"mdimporting" );
NSTask *mdimport = [[[NSTask alloc] init] autorelease];
[mdimport setLaunchPath:@"/usr/bin/mdimport"];
[mdimport setArguments:[NSArray arrayWithObject:installationPath]];
@try
{
[mdimport launch];
[mdimport waitUntilExit];
}
@catch (NSException * launchException)
{
// No big deal.
SULog(@"Sparkle Error: %@", [launchException description]);
}
}
#define SUNotifyDictHostKey @"SUNotifyDictHost"
#define SUNotifyDictErrorKey @"SUNotifyDictError"
#define SUNotifyDictDelegateKey @"SUNotifyDictDelegate"
+ (void)finishInstallationToPath:(NSString *)installationPath withResult:(BOOL)result host:(SUHost *)host error:(NSError *)error delegate:delegate
{
if (result)
{
[self mdimportInstallationPath:installationPath];
if ([delegate respondsToSelector:@selector(installerFinishedForHost:)])
[delegate performSelectorOnMainThread: @selector(installerFinishedForHost:) withObject: host waitUntilDone: NO];
}
else
{
if ([delegate respondsToSelector:@selector(installerForHost:failedWithError:)])
[self performSelectorOnMainThread: @selector(notifyDelegateOfFailure:) withObject: [NSDictionary dictionaryWithObjectsAndKeys: host, SUNotifyDictHostKey, error, SUNotifyDictErrorKey, delegate, SUNotifyDictDelegateKey, nil] waitUntilDone: NO];
}
}
+(void) notifyDelegateOfFailure: (NSDictionary*)dict
{
[[dict objectForKey: SUNotifyDictDelegateKey] installerForHost: [dict objectForKey: SUNotifyDictHostKey] failedWithError: [dict objectForKey: SUNotifyDictErrorKey]];
}
@end