From 98832c49145411c5e13e118fe686ff6c52bcbbe6 Mon Sep 17 00:00:00 2001 From: Andy Matuschak Date: Tue, 15 Jul 2008 23:26:06 -0700 Subject: [PATCH] Beginnings of insane SUHost-based refactoring to get rid of NSBundle+Sparkle. More super-unstable refactorings to come... --- NSBundle+SUAdditions.h | 59 -------- NSBundle+SUAdditions.m | 89 ----------- SUAutomaticUpdateAlert.h | 6 +- SUAutomaticUpdateAlert.m | 14 +- SUAutomaticUpdateDriver.m | 8 +- SUBasicUpdateDriver.h | 9 +- SUBasicUpdateDriver.m | 71 +++++---- SUHost.h | 32 ++++ SUHost.m | 169 +++++++++++++++++++++ SUInstaller.h | 9 +- SUInstaller.m | 28 ++-- SUPackageInstaller.h | 2 +- SUPackageInstaller.m | 4 +- SUPlainInstaller.h | 2 +- SUPlainInstaller.m | 12 +- SUStatusController.h | 5 +- SUStatusController.m | 12 +- SUSystemProfiler.h | 2 +- SUSystemProfiler.m | 6 +- SUUIBasedUpdateDriver.m | 24 +-- SUUpdateAlert.h | 6 +- SUUpdateAlert.m | 22 +-- SUUpdateDriver.h | 5 +- SUUpdateDriver.m | 4 +- SUUpdatePermissionPrompt.h | 5 +- SUUpdatePermissionPrompt.m | 22 +-- SUUpdater.h | 27 ++-- SUUpdater.m | 240 +++++++++++++++++++----------- SUUserDefaults.h | 41 ----- SUUserDefaults.m | 97 ------------ SUUserInitiatedUpdateDriver.m | 6 +- SUWindowController.h | 3 +- SUWindowController.m | 2 +- Sparkle.h | 3 +- Sparkle.xcodeproj/project.pbxproj | 26 ++-- 35 files changed, 526 insertions(+), 546 deletions(-) delete mode 100644 NSBundle+SUAdditions.h delete mode 100644 NSBundle+SUAdditions.m create mode 100644 SUHost.h create mode 100644 SUHost.m delete mode 100644 SUUserDefaults.h delete mode 100644 SUUserDefaults.m diff --git a/NSBundle+SUAdditions.h b/NSBundle+SUAdditions.h deleted file mode 100644 index ed2475c42..000000000 --- a/NSBundle+SUAdditions.h +++ /dev/null @@ -1,59 +0,0 @@ -// -// NSBundle+SUAdditions.h -// Sparkle -// -// Created by Andy Matuschak on 12/21/07. -// Copyright 2007 Andy Matuschak. All rights reserved. -// - -#ifndef NSBUNDLE_PLUS_ADDITIONS_H -#define NSBUNDLE_PLUS_ADDITIONS_H - -#import - -@interface NSBundle (SUAdditions) -/*! - @method - @abstract Returns a name for the bundle suitable for display to the user. - @discussion This is performed by asking NSFileManager for the display name of the bundle. -*/ -- (NSString *)name; - -/*! - @method - @abstract Returns the current internal version of the bundle. - @discussion This uses the CFBundleVersion info value. This string is not appropriate for display to users: use -displayVersion instead. -*/ -- (NSString *)version; - -/*! - @method - @abstract Returns the bundle's version, suitable for display to the user. - @discussion If the CFBundleShortVersionString is available and different from the CFBundleVersion, this looks like CFBundleShortVersionString (CFBundleVersion). If the version strings are the same or CFBundleShortVersionString is not defined, this is equivalent to -version. -*/ -- (NSString *)displayVersion; - -/*! - @method - @abstract Returns a suitable icon for this bundle. - @discussion Uses the CFBundleIconFile icon if defined; otherwise, uses the default application icon. -*/ -- (NSImage *)icon; - -/*! - @method - @abstract Returns whether the application is running from a disk image. -*/ -- (BOOL)isRunningFromDiskImage; - -/*! - @method - @abstract Returns a profile of the users system useful for statistical purposes. - @discussion Returns an array of dictionaries; each dictionary represents a piece of data and has keys "key", "visibleKey", "value", and "visibleValue". -*/ -- (NSArray *)systemProfile; - -- (NSString *)publicDSAKey; -@end - -#endif diff --git a/NSBundle+SUAdditions.m b/NSBundle+SUAdditions.m deleted file mode 100644 index de3c0ce1b..000000000 --- a/NSBundle+SUAdditions.m +++ /dev/null @@ -1,89 +0,0 @@ -// -// NSBundle+SUAdditions.m -// Sparkle -// -// Created by Andy Matuschak on 12/21/07. -// Copyright 2007 Andy Matuschak. All rights reserved. -// - -#import "Sparkle.h" -#import "NSBundle+SUAdditions.h" - -#ifndef NSAppKitVersionNumber10_4 -#define NSAppKitVersionNumber10_4 824 -#endif - -@implementation NSBundle (SUAdditions) - -- (NSString *)name -{ - NSString *name = [self objectForInfoDictionaryKey:@"CFBundleDisplayName"]; - if (name) return name; - - name = [self objectForInfoDictionaryKey:@"CFBundleName"]; - if (name) return name; - - return [[[NSFileManager defaultManager] displayNameAtPath:[self bundlePath]] stringByDeletingPathExtension]; -} - -- (NSString *)version -{ - return [self objectForInfoDictionaryKey:@"CFBundleVersion"]; -} - -- (NSString *)displayVersion -{ - NSString *shortVersionString = [self objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - if (shortVersionString) - return shortVersionString; - else - return [self version]; // Fall back on the normal version string. -} - -- (NSImage *)icon -{ - // Cache the application icon. - NSString *iconPath = [self pathForResource:[self objectForInfoDictionaryKey:@"CFBundleIconFile"] ofType:@"icns"]; - // According to the OS X docs, "CFBundleIconFile - This key identifies the file containing - // the icon for the bundle. The filename you specify does not need to include the .icns - // extension, although it may." - // - // However, if it *does* include the '.icns' the above method fails (tested on OS X 10.3.9) so we'll also try: - if (!iconPath) - iconPath = [self pathForResource:[self objectForInfoDictionaryKey:@"CFBundleIconFile"] ofType: nil]; - NSImage *icon = [[[NSImage alloc] initWithContentsOfFile:iconPath] autorelease]; - // Use a default icon if none is defined. - if (!icon) { icon = [NSImage imageNamed:@"NSDefaultApplicationIcon"]; } - return icon; -} - -- (BOOL)isRunningFromDiskImage -{ - // This check causes crashes on 10.3; for now, we'll just skip it. - if (floor(NSAppKitVersionNumber) < NSAppKitVersionNumber10_4) - return NO; - - NSDictionary *pathProperties = [[NSWorkspace sharedWorkspace] propertiesForPath:[self bundlePath]]; - BOOL isDiskImage = [pathProperties objectForKey:NSWorkspace_RBimagefilepath] != nil; - BOOL isFileVault = [[pathProperties objectForKey:NSWorkspace_RBmntonname] hasPrefix:@"/Users/"]; - return isDiskImage && !isFileVault; -} - -- (NSString *)publicDSAKey -{ - // Maybe the key is just a string in the Info.plist. - NSString *key = [self objectForInfoDictionaryKey:SUPublicDSAKeyKey]; - if (key) { return key; } - - // More likely, we've got a reference to a Resources file by filename: - NSString *keyFilename = [self objectForInfoDictionaryKey:SUPublicDSAKeyFileKey]; - if (!keyFilename) { return nil; } - return [NSString stringWithContentsOfFile:[self pathForResource:keyFilename ofType:nil]]; -} - -- (NSArray *)systemProfile -{ - return [[SUSystemProfiler sharedSystemProfiler] systemProfileArrayForHostBundle:self]; -} - -@end diff --git a/SUAutomaticUpdateAlert.h b/SUAutomaticUpdateAlert.h index 76fb35f48..dc2183e5f 100644 --- a/SUAutomaticUpdateAlert.h +++ b/SUAutomaticUpdateAlert.h @@ -18,14 +18,14 @@ typedef enum SUDoNotInstallChoice } SUAutomaticInstallationChoice; -@class SUAppcastItem; +@class SUAppcastItem, SUHost; @interface SUAutomaticUpdateAlert : SUWindowController { SUAppcastItem *updateItem; id delegate; - NSBundle *hostBundle; + SUHost *host; } -- (id)initWithAppcastItem:(SUAppcastItem *)item hostBundle:(NSBundle *)hostBundle delegate:delegate; +- (id)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)hostBundle delegate:delegate; - (IBAction)installNow:sender; - (IBAction)installLater:sender; - (IBAction)doNotInstall:sender; diff --git a/SUAutomaticUpdateAlert.m b/SUAutomaticUpdateAlert.m index 824460df8..99a0aa6b4 100644 --- a/SUAutomaticUpdateAlert.m +++ b/SUAutomaticUpdateAlert.m @@ -11,14 +11,14 @@ @implementation SUAutomaticUpdateAlert -- (id)initWithAppcastItem:(SUAppcastItem *)item hostBundle:(NSBundle *)hb delegate:del; +- (id)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)hb delegate:del; { - self = [super initWithHostBundle:hb windowNibName:@"SUAutomaticUpdateAlert"]; + self = [super initWithHost:hb windowNibName:@"SUAutomaticUpdateAlert"]; if (self) { updateItem = [item retain]; delegate = del; - hostBundle = [hb retain]; + host = [hb retain]; [self setShouldCascadeWindows:NO]; [[self window] center]; } @@ -27,7 +27,7 @@ - (id)initWithAppcastItem:(SUAppcastItem *)item hostBundle:(NSBundle *)hb delega - (void)dealloc { - [hostBundle release]; + [host release]; [updateItem release]; [super dealloc]; } @@ -53,17 +53,17 @@ - (IBAction)doNotInstall:sender - (NSImage *)applicationIcon { - return [hostBundle icon]; + return [host icon]; } - (NSString *)titleText { - return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ is ready to install!", nil), [hostBundle name]]; + return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ is ready to install!", nil), [host name]]; } - (NSString *)descriptionText { - return [NSString stringWithFormat:SULocalizedString(@"%1$@ %2$@ has been downloaded and is ready to use! Would you like to install it and relaunch %1$@ now?", nil), [hostBundle name], [hostBundle displayVersion]]; + return [NSString stringWithFormat:SULocalizedString(@"%1$@ %2$@ has been downloaded and is ready to use! Would you like to install it and relaunch %1$@ now?", nil), [host name], [host displayVersion]]; } @end diff --git a/SUAutomaticUpdateDriver.m b/SUAutomaticUpdateDriver.m index 56dae07e3..a9c842e38 100644 --- a/SUAutomaticUpdateDriver.m +++ b/SUAutomaticUpdateDriver.m @@ -13,7 +13,7 @@ @implementation SUAutomaticUpdateDriver - (void)unarchiverDidFinish:(SUUnarchiver *)ua { - alert = [[SUAutomaticUpdateAlert alloc] initWithAppcastItem:updateItem hostBundle:hostBundle delegate:self]; + alert = [[SUAutomaticUpdateAlert alloc] initWithAppcastItem:updateItem host:host delegate:self]; if ([NSApp isActive]) [[alert window] makeKeyAndOrderFront:self]; else @@ -40,7 +40,7 @@ - (void)automaticUpdateAlert:(SUAutomaticUpdateAlert *)aua finishedWithChoice:(S break; case SUDoNotInstallChoice: - [[SUUserDefaults standardUserDefaults] setObject:[updateItem versionString] forKey:SUSkippedVersionKey]; + [host setObject:[updateItem versionString] forUserDefaultsKey:SUSkippedVersionKey]; [self abortUpdate]; break; } @@ -59,9 +59,9 @@ - (void)applicationWillTerminate:(NSNotification *)note [self installUpdate]; } -- (void)installerFinishedForHostBundle:(NSBundle *)hb +- (void)installerFinishedForHost:(SUHost *)aHost { - if (hb != hostBundle) { return; } + if (aHost != host) { return; } if (!postponingInstallation) [self relaunchHostApp]; } diff --git a/SUBasicUpdateDriver.h b/SUBasicUpdateDriver.h index ddafed6c1..a047be2c5 100644 --- a/SUBasicUpdateDriver.h +++ b/SUBasicUpdateDriver.h @@ -12,9 +12,8 @@ #import #import "SUUpdateDriver.h" -@class SUAppcastItem, SUUnarchiver, SUAppcast, SUUnarchiver; +@class SUAppcastItem, SUUnarchiver, SUAppcast, SUUnarchiver, SUHost; @interface SUBasicUpdateDriver : SUUpdateDriver { - NSBundle *hostBundle; SUAppcastItem *updateItem; NSURLDownload *download; @@ -23,7 +22,7 @@ NSString *relaunchPath; } -- (void)checkForUpdatesAtURL:(NSURL *)appcastURL hostBundle:(NSBundle *)hb; +- (void)checkForUpdatesAtURL:(NSURL *)appcastURL host:(SUHost *)hb; - (void)appcastDidFinishLoading:(SUAppcast *)ac; - (void)appcast:(SUAppcast *)ac failedToLoadWithError:(NSError *)error; @@ -45,8 +44,8 @@ - (void)unarchiverDidFail:(SUUnarchiver *)ua; - (void)installUpdate; -- (void)installerFinishedForHostBundle:(NSBundle *)hb; -- (void)installerForHostBundle:(NSBundle *)hb failedWithError:(NSError *)error; +- (void)installerFinishedForHost:(SUHost *)hb; +- (void)installerForHost:(SUHost *)hb failedWithError:(NSError *)error; - (void)relaunchHostApp; - (void)cleanUp; diff --git a/SUBasicUpdateDriver.m b/SUBasicUpdateDriver.m index eb2990b5c..cc5a191f3 100644 --- a/SUBasicUpdateDriver.m +++ b/SUBasicUpdateDriver.m @@ -6,18 +6,17 @@ // Copyright 2008 Andy Matuschak. All rights reserved. // -#import "SUBasicUpdateDriver.h" #import "Sparkle.h" +#import "SUBasicUpdateDriver.h" @implementation SUBasicUpdateDriver -- (void)checkForUpdatesAtURL:(NSURL *)appcastURL hostBundle:(NSBundle *)hb +- (void)checkForUpdatesAtURL:(NSURL *)appcastURL host:(SUHost *)aHost { - hostBundle = [hb retain]; - - if ([hostBundle isRunningFromDiskImage]) + [super checkForUpdatesAtURL:appcastURL host:aHost]; + if ([aHost isRunningFromDiskImage]) { - [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURunningFromDiskImageError userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:SULocalizedString(@"%1$@ can't be updated when it's running from a disk image. Move %1$@ to your Applications folder, relaunch it from there, and try again.", nil), [hostBundle name]] forKey:NSLocalizedDescriptionKey]]]; + [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURunningFromDiskImageError userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:SULocalizedString(@"%1$@ can't be updated when it's running from a disk image. Move %1$@ to your Applications folder, relaunch it from there, and try again.", nil), [aHost name]] forKey:NSLocalizedDescriptionKey]]]; return; } @@ -26,9 +25,9 @@ - (void)checkForUpdatesAtURL:(NSURL *)appcastURL hostBundle:(NSBundle *)hb [appcast release]; [appcast setDelegate:self]; - [appcast setUserAgentString:[NSString stringWithFormat: @"%@/%@ Sparkle/1.5b4", [hostBundle name], [hostBundle displayVersion]]]; + [appcast setUserAgentString:[NSString stringWithFormat: @"%@/%@ Sparkle/1.5b4", [aHost name], [aHost displayVersion]]]; [appcast fetchAppcastFromURL:appcastURL]; - [[SUUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:SULastCheckTimeKey]; + [host setObject:[NSDate date] forUserDefaultsKey:SULastCheckTimeKey]; } - (id )_versionComparator @@ -36,8 +35,8 @@ - (void)checkForUpdatesAtURL:(NSURL *)appcastURL hostBundle:(NSBundle *)hb id comparator = nil; // Give the delegate a chance to provide a custom version comparator - if ([delegate respondsToSelector:@selector(versionComparatorForHostBundle:)]) - comparator = [delegate versionComparatorForHostBundle:hostBundle]; + if ([delegate respondsToSelector:@selector(versionComparatorForHost:)]) + comparator = [delegate versionComparatorForHost:host]; // If we don't get a comparator from the delegate, use the default comparator if (!comparator) @@ -48,7 +47,7 @@ - (void)checkForUpdatesAtURL:(NSURL *)appcastURL hostBundle:(NSBundle *)hb - (BOOL)isItemNewer:(SUAppcastItem *)ui { - return [[self _versionComparator] compareVersion:[hostBundle version] toVersion:[ui versionString]] == NSOrderedAscending; + return [[self _versionComparator] compareVersion:[host version] toVersion:[ui versionString]] == NSOrderedAscending; } - (BOOL)hostSupportsItem:(SUAppcastItem *)ui @@ -59,7 +58,7 @@ - (BOOL)hostSupportsItem:(SUAppcastItem *)ui - (BOOL)itemContainsSkippedVersion:(SUAppcastItem *)ui { - NSString *skippedVersion = [[SUUserDefaults standardUserDefaults] objectForKey:SUSkippedVersionKey]; + NSString *skippedVersion = [host objectForUserDefaultsKey:SUSkippedVersionKey]; if (skippedVersion == nil) { return NO; } return [[self _versionComparator] compareVersion:[ui versionString] toVersion:skippedVersion] != NSOrderedDescending; } @@ -71,13 +70,13 @@ - (BOOL)itemContainsValidUpdate:(SUAppcastItem *)ui - (void)appcastDidFinishLoading:(SUAppcast *)ac { - if ([delegate respondsToSelector:@selector(appcastDidFinishLoading:forHostBundle:)]) - [delegate appcastDidFinishLoading:ac forHostBundle:hostBundle]; + if ([delegate respondsToSelector:@selector(appcastDidFinishLoading:forHost:)]) + [delegate appcastDidFinishLoading:ac forHost:host]; // Now we have to find the best valid update in the appcast. - if ([delegate respondsToSelector:@selector(bestValidUpdateInAppcast:forHostBundle:)]) // Does the delegate want to handle it? + if ([delegate respondsToSelector:@selector(bestValidUpdateInAppcast:forHost:)]) // Does the delegate want to handle it? { - updateItem = [delegate bestValidUpdateInAppcast:ac forHostBundle:hostBundle]; + updateItem = [delegate bestValidUpdateInAppcast:ac forHost:host]; } else // If not, we'll take care of it ourselves. { @@ -106,16 +105,16 @@ - (void)appcast:(SUAppcast *)ac failedToLoadWithError:(NSError *)error - (void)didFindValidUpdate { - if ([delegate respondsToSelector:@selector(didFindValidUpdate:toHostBundle:)]) - [delegate didFindValidUpdate:updateItem toHostBundle:hostBundle]; + if ([delegate respondsToSelector:@selector(didFindValidUpdate:toHost:)]) + [delegate didFindValidUpdate:updateItem toHost:host]; [self downloadUpdate]; } - (void)didNotFindUpdate { - if ([delegate respondsToSelector:@selector(didNotFindUpdateToHostBundle:)]) - [delegate didNotFindUpdateToHostBundle:hostBundle]; - [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUNoUpdateError userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:SULocalizedString(@"You already have the newest version of %@.", nil), [hostBundle name]] forKey:NSLocalizedDescriptionKey]]]; + if ([delegate respondsToSelector:@selector(didNotFindUpdateToHost:)]) + [delegate didNotFindUpdateToHost:host]; + [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUNoUpdateError userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:SULocalizedString(@"You already have the newest version of %@.", nil), [host name]] forKey:NSLocalizedDescriptionKey]]]; } - (void)downloadUpdate @@ -131,7 +130,7 @@ - (void)download:(NSURLDownload *)d decideDestinationWithSuggestedFilename:(NSSt // We create a temporary directory in /tmp and stick the file there. // Not using a GUID here because hdiutil for some reason chokes on GUIDs. Too long? I really have no idea. - NSString *prefix = [NSString stringWithFormat:@"%@ %@ Update", [hostBundle name], [hostBundle version]]; + NSString *prefix = [NSString stringWithFormat:@"%@ %@ Update", [host name], [host version]]; NSString *tempDir = [NSTemporaryDirectory() stringByAppendingPathComponent:prefix]; int cnt=1; while ([[NSFileManager defaultManager] fileExistsAtPath:tempDir] && cnt <= 999999) @@ -164,9 +163,9 @@ - (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error - (void)extractUpdate { // DSA verification, if activated by the developer - if ([[hostBundle objectForInfoDictionaryKey:SUExpectsDSASignatureKey] boolValue]) + if ([[host objectForInfoDictionaryKey:SUExpectsDSASignatureKey] boolValue]) { - if (![[NSFileManager defaultManager] validatePath:downloadPath withEncodedDSASignature:[updateItem DSASignature] withPublicDSAKey:[hostBundle publicDSAKey]]) + if (![[NSFileManager defaultManager] validatePath:downloadPath withEncodedDSASignature:[updateItem DSASignature] withPublicDSAKey:[host publicDSAKey]]) { [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUSignatureError userInfo:[NSDictionary dictionaryWithObject:@"The update is improperly signed." forKey:NSLocalizedDescriptionKey]]]; return; @@ -201,8 +200,8 @@ - (BOOL)shouldInstallSynchronously { return NO; } - (void)installUpdate { - if ([delegate respondsToSelector:@selector(updateWillInstall:toHostBundle:)]) - [delegate updateWillInstall:updateItem toHostBundle:hostBundle]; + if ([delegate respondsToSelector:@selector(updateWillInstall:toHost:)]) + [delegate updateWillInstall:updateItem toHost:host]; // Copy the relauncher into a temporary directory so we can get to it after the new version's installed. NSString *relaunchPathToCopy = [[NSBundle bundleForClass:[self class]] pathForResource:@"relaunch" ofType:@""]; NSString *targetPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[relaunchPathToCopy lastPathComponent]]; @@ -211,12 +210,12 @@ - (void)installUpdate if ([[NSFileManager defaultManager] copyPath:relaunchPathToCopy toPath:targetPath handler:nil]) relaunchPath = [targetPath retain]; - [SUInstaller installFromUpdateFolder:[downloadPath stringByDeletingLastPathComponent] overHostBundle:hostBundle delegate:self synchronously:[self shouldInstallSynchronously]]; + [SUInstaller installFromUpdateFolder:[downloadPath stringByDeletingLastPathComponent] overHost:host delegate:self synchronously:[self shouldInstallSynchronously]]; } -- (void)installerFinishedForHostBundle:(NSBundle *)hb +- (void)installerFinishedForHost:(SUHost *)aHost { - if (hb != hostBundle) { return; } + if (aHost != host) { return; } [self relaunchHostApp]; } @@ -224,13 +223,13 @@ - (void)relaunchHostApp { // Give the host app an opportunity to postpone the relaunch. static BOOL postponedOnce = NO; - if (!postponedOnce && [delegate respondsToSelector:@selector(shouldPostponeRelaunchForUpdate:toHostBundle:untilInvoking:)]) + if (!postponedOnce && [delegate respondsToSelector:@selector(shouldPostponeRelaunchForUpdate:toHost:untilInvoking:)]) { NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[[self class] instanceMethodSignatureForSelector:@selector(relaunchHostApp)] retain]]; [invocation setSelector:@selector(relaunchHostApp)]; [invocation setTarget:self]; postponedOnce = YES; - if ([delegate shouldPostponeRelaunchForUpdate:updateItem toHostBundle:hostBundle untilInvoking:invocation]) + if ([delegate shouldPostponeRelaunchForUpdate:updateItem toHost:host untilInvoking:invocation]) return; } @@ -243,12 +242,12 @@ - (void)relaunchHostApp if(!relaunchPath || ![[NSFileManager defaultManager] fileExistsAtPath:relaunchPath]) { // Note that we explicitly use the host app's name here, since updating plugin for Mail relaunches Mail, not just the plugin. - [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:SULocalizedString(@"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@.", nil), [[NSBundle mainBundle] name]], NSLocalizedDescriptionKey, [NSString stringWithFormat:@"Couldn't find the relauncher (expected to find it at %@)", relaunchPath], NSLocalizedFailureReasonErrorKey, nil]]]; + [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:SULocalizedString(@"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@.", nil), [host name]], NSLocalizedDescriptionKey, [NSString stringWithFormat:@"Couldn't find the relauncher (expected to find it at %@)", relaunchPath], NSLocalizedFailureReasonErrorKey, nil]]]; // We intentionally don't abandon the update here so that the host won't initiate another. return; } - [NSTask launchedTaskWithLaunchPath:relaunchPath arguments:[NSArray arrayWithObjects:[hostBundle bundlePath], [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]], nil]]; + [NSTask launchedTaskWithLaunchPath:relaunchPath arguments:[NSArray arrayWithObjects:[host bundlePath], [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]], nil]]; [NSApp terminate:self]; } @@ -258,9 +257,9 @@ - (void)cleanUp [[NSFileManager defaultManager] removeFileAtPath:[downloadPath stringByDeletingLastPathComponent] handler:nil]; } -- (void)installerForHostBundle:(NSBundle *)hb failedWithError:(NSError *)error +- (void)installerForHost:(SUHost *)aHost failedWithError:(NSError *)error { - if (hb != hostBundle) { return; } + if (aHost != host) { return; } [[NSFileManager defaultManager] removeFileAtPath:relaunchPath handler:NULL]; // Clean up the copied relauncher. [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:SULocalizedString(@"An error occurred while installing the update. Please try again later.", nil), NSLocalizedDescriptionKey, [error localizedDescription], NSLocalizedFailureReasonErrorKey, nil]]]; } @@ -284,7 +283,7 @@ - (void)abortUpdateWithError:(NSError *)error - (void)dealloc { - [hostBundle release]; + [host release]; [download release]; [downloadPath release]; [relaunchPath release]; diff --git a/SUHost.h b/SUHost.h new file mode 100644 index 000000000..d2e769fc1 --- /dev/null +++ b/SUHost.h @@ -0,0 +1,32 @@ +// +// SUHost.h +// Sparkle +// +// Copyright 2008 Andy Matuschak. All rights reserved. +// + +#import "Sparkle.h" + +@interface SUHost : NSObject +{ + NSBundle *bundle; +} + +- (id)initWithBundle:(NSBundle *)aBundle; +- (NSBundle *)bundle; +- (NSString *)bundlePath; +- (NSString *)name; +- (NSString *)version; +- (NSString *)displayVersion; +- (NSImage *)icon; +- (BOOL)isRunningFromDiskImage; +- (NSString *)publicDSAKey; +- (NSArray *)systemProfile; + +- (id)objectForInfoDictionaryKey:(NSString *)key; +- (BOOL)boolForInfoDictionaryKey:(NSString *)key; +- (id)objectForUserDefaultsKey:(NSString *)defaultName; +- (void)setObject:(id)value forUserDefaultsKey:(NSString *)defaultName; +- (BOOL)boolForUserDefaultsKey:(NSString *)defaultName; +- (void)setBool:(BOOL)value forUserDefaultsKey:(NSString *)defaultName; +@end diff --git a/SUHost.m b/SUHost.m new file mode 100644 index 000000000..c35c86a27 --- /dev/null +++ b/SUHost.m @@ -0,0 +1,169 @@ +// +// SUHost.m +// Sparkle +// +// Copyright 2008 Andy Matuschak. All rights reserved. +// + +#import "Sparkle.h" + +@implementation SUHost + +- (id)initWithBundle:(NSBundle *)aBundle +{ + if (aBundle == nil) aBundle = [NSBundle mainBundle]; + if ((self = [super init])) + { + bundle = [aBundle retain]; + } + return self; +} + +- (void)dealloc +{ + [bundle release]; + [super dealloc]; +} + +- (NSBundle *)bundle +{ + return bundle; +} + +- (NSString *)bundlePath +{ + return [bundle bundlePath]; +} + +- (NSString *)name +{ + NSString *name = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + if (name) return name; + + name = [self objectForInfoDictionaryKey:@"CFBundleName"]; + if (name) return name; + + return [[[NSFileManager defaultManager] displayNameAtPath:[bundle bundlePath]] stringByDeletingPathExtension]; +} + +- (NSString *)version +{ + return [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]; +} + +- (NSString *)displayVersion +{ + NSString *shortVersionString = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + if (shortVersionString) + return shortVersionString; + else + return [self version]; // Fall back on the normal version string. +} + +- (NSImage *)icon +{ + // Cache the application icon. + NSString *iconPath = [bundle pathForResource:[bundle objectForInfoDictionaryKey:@"CFBundleIconFile"] ofType:@"icns"]; + // According to the OS X docs, "CFBundleIconFile - This key identifies the file containing + // the icon for the bundle. The filename you specify does not need to include the .icns + // extension, although it may." + // + // However, if it *does* include the '.icns' the above method fails (tested on OS X 10.3.9) so we'll also try: + if (!iconPath) + iconPath = [bundle pathForResource:[bundle objectForInfoDictionaryKey:@"CFBundleIconFile"] ofType: nil]; + NSImage *icon = [[[NSImage alloc] initWithContentsOfFile:iconPath] autorelease]; + // Use a default icon if none is defined. + if (!icon) { icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)]; } + return icon; +} + +- (BOOL)isRunningFromDiskImage +{ + // This check causes crashes on 10.3; for now, we'll just skip it. + if (floor(NSAppKitVersionNumber) < NSAppKitVersionNumber10_4) + return NO; + + NSDictionary *pathProperties = [[NSWorkspace sharedWorkspace] propertiesForPath:[bundle bundlePath]]; + BOOL isDiskImage = [pathProperties objectForKey:NSWorkspace_RBimagefilepath] != nil; + BOOL isFileVault = [[pathProperties objectForKey:NSWorkspace_RBmntonname] hasPrefix:@"/Users/"]; + return isDiskImage && !isFileVault; +} + +- (NSString *)publicDSAKey +{ + // Maybe the key is just a string in the Info.plist. + NSString *key = [bundle objectForInfoDictionaryKey:SUPublicDSAKeyKey]; + if (key) { return key; } + + // More likely, we've got a reference to a Resources file by filename: + NSString *keyFilename = [self objectForInfoDictionaryKey:SUPublicDSAKeyFileKey]; + if (!keyFilename) { return nil; } + return [NSString stringWithContentsOfFile:[bundle pathForResource:keyFilename ofType:nil]]; +} + +- (NSArray *)systemProfile +{ + return [[SUSystemProfiler sharedSystemProfiler] systemProfileArrayForHost:self]; +} + +- (id)objectForInfoDictionaryKey:(NSString *)key +{ + return [bundle objectForInfoDictionaryKey:key]; +} + +- (BOOL)boolForInfoDictionaryKey:(NSString *)key +{ + return [[self objectForInfoDictionaryKey:key] boolValue]; +} + +- (id)objectForUserDefaultsKey:(NSString *)defaultName +{ + CFPropertyListRef obj = CFPreferencesCopyAppValue((CFStringRef)defaultName, (CFStringRef)[bundle bundleIdentifier]); + // Under Tiger, CFPreferencesCopyAppValue doesn't get values from NSRegistratioDomain, so anything + // passed into -[NSUserDefaults registerDefaults:] is ignored. The following line falls + // back to using NSUserDefaults, but only if the host bundle is the main bundle, and no value + // is found elsewhere. + if (obj == NULL && bundle != [NSBundle mainBundle]) + obj = [[NSUserDefaults standardUserDefaults] objectForKey:defaultName]; +#if MAC_OS_X_VERSION_MIN_REQUIRED > 1050 + return [NSMakeCollectable(obj) autorelease]; +#else + return [(id)obj autorelease]; +#endif +} + +- (void)setObject:(id)value forUserDefaultsKey:(NSString *)defaultName; +{ + CFPreferencesSetValue((CFStringRef)defaultName, value, (CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + CFPreferencesSynchronize((CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + // If anything's bound to this through an NSUserDefaultsController, it won't know that anything's changed. + // We can't get an NSUserDefaults object for anything other than the standard one for the app, so this won't work for bundles. + // But it's the best we can do: this will make NSUserDefaultsControllers know about the changes that have been made. + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +- (BOOL)boolForUserDefaultsKey:(NSString *)defaultName +{ + BOOL value; + CFPropertyListRef plr = CFPreferencesCopyAppValue((CFStringRef)defaultName, (CFStringRef)[bundle bundleIdentifier]); + if (plr == NULL) + value = NO; + else { + value = (BOOL)CFBooleanGetValue((CFBooleanRef)plr); + CFRelease(plr); + } + + return value; +} + +- (void)setBool:(BOOL)value forUserDefaultsKey:(NSString *)defaultName +{ + CFPreferencesSetValue((CFStringRef)defaultName, (CFBooleanRef)[NSNumber numberWithBool:value], (CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + CFPreferencesSynchronize((CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + // If anything's bound to this through an NSUserDefaultsController, it won't know that anything's changed. + // We can't get an NSUserDefaults object for anything other than the standard one for the app, so this won't work for bundles. + // But it's the best we can do: this will make NSUserDefaultsControllers know about the changes that have been made. + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +@end diff --git a/SUInstaller.h b/SUInstaller.h index 08bfc9743..cbfb9f770 100644 --- a/SUInstaller.h +++ b/SUInstaller.h @@ -11,14 +11,15 @@ #import +@class SUHost; @interface SUInstaller : NSObject { } -+ (void)installFromUpdateFolder:(NSString *)updateFolder overHostBundle:(NSBundle *)hostBundle delegate:delegate synchronously:(BOOL)synchronously; -+ (void)_finishInstallationWithResult:(BOOL)result hostBundle:(NSBundle *)hostBundle error:(NSError *)error delegate:delegate; ++ (void)installFromUpdateFolder:(NSString *)updateFolder overHost:(SUHost *)host delegate:delegate synchronously:(BOOL)synchronously; ++ (void)_finishInstallationWithResult:(BOOL)result host:(SUHost *)host error:(NSError *)error delegate:delegate; @end @interface NSObject (SUInstallerDelegateInformalProtocol) -- (void)installerFinishedForHostBundle:(NSBundle *)hostBundle; -- (void)installerForHostBundle:(NSBundle *)hostBundle failedWithError:(NSError *)error; +- (void)installerFinishedForHost:(SUHost *)host; +- (void)installerForHost:(SUHost *)host failedWithError:(NSError *)error; @end #endif diff --git a/SUInstaller.m b/SUInstaller.m index 54a465daf..d35488c8d 100644 --- a/SUInstaller.m +++ b/SUInstaller.m @@ -11,15 +11,15 @@ #import "SUPackageInstaller.h" NSString *SUInstallerPathKey = @"SUInstallerPath"; -NSString *SUInstallerHostBundleKey = @"SUInstallerHostBundle"; +NSString *SUInstallerHostKey = @"SUInstallerHost"; NSString *SUInstallerDelegateKey = @"SUInstallerDelegate"; @implementation SUInstaller -+ (void)installFromUpdateFolder:(NSString *)updateFolder overHostBundle:(NSBundle *)hostBundle delegate:delegate synchronously:(BOOL)synchronously ++ (void)installFromUpdateFolder:(NSString *)updateFolder overHost:(SUHost *)host delegate:delegate synchronously:(BOOL)synchronously { // Search subdirectories for the application - NSString *currentFile, *newAppDownloadPath = nil, *bundleFileName = [[hostBundle bundlePath] lastPathComponent], *alternateBundleFileName = [[hostBundle name] stringByAppendingPathExtension:[[hostBundle bundlePath] pathExtension]]; + NSString *currentFile, *newAppDownloadPath = nil, *bundleFileName = [[host bundlePath] lastPathComponent], *alternateBundleFileName = [[host name] stringByAppendingPathExtension:[[host bundlePath] pathExtension]]; BOOL isPackage = NO; NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:updateFolder]; while ((currentFile = [dirEnum nextObject])) @@ -42,7 +42,7 @@ + (void)installFromUpdateFolder:(NSString *)updateFolder overHostBundle:(NSBundl // Some DMGs have symlinks into /Applications! That's no good! And there's no point in looking in bundles. if ([[NSFileManager defaultManager] isAliasFolderAtPath:currentPath] || - [[currentFile pathExtension] isEqualToString:[[hostBundle bundlePath] pathExtension]] || + [[currentFile pathExtension] isEqualToString:[[host bundlePath] pathExtension]] || [[currentFile pathExtension] isEqualToString:@"pkg"] || [[currentFile pathExtension] isEqualToString:@"mpkg"]) { @@ -52,34 +52,34 @@ + (void)installFromUpdateFolder:(NSString *)updateFolder overHostBundle:(NSBundl if (newAppDownloadPath == nil) { - [self _finishInstallationWithResult:NO hostBundle:hostBundle error:[NSError errorWithDomain:SUSparkleErrorDomain code:SUMissingUpdateError userInfo:[NSDictionary dictionaryWithObject:@"Couldn't find an appropriate update in the downloaded package." forKey:NSLocalizedDescriptionKey]] delegate:delegate]; + [self _finishInstallationWithResult: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]) performInstallationWithPath:newAppDownloadPath hostBundle:hostBundle delegate:delegate synchronously:synchronously]; + [(isPackage ? [SUPackageInstaller class] : [SUPlainInstaller class]) performInstallationWithPath:newAppDownloadPath host:host delegate:delegate synchronously:synchronously]; } } -+ (void)_mdimportBundle:(NSBundle *)bundle ++ (void)_mdimportHost:(SUHost *)host { NSTask *mdimport = [[[NSTask alloc] init] autorelease]; [mdimport setLaunchPath:@"/usr/bin/mdimport"]; - [mdimport setArguments:[NSArray arrayWithObject:[bundle bundlePath]]]; + [mdimport setArguments:[NSArray arrayWithObject:[host bundlePath]]]; [mdimport launch]; } -+ (void)_finishInstallationWithResult:(BOOL)result hostBundle:(NSBundle *)hostBundle error:(NSError *)error delegate:delegate ++ (void)_finishInstallationWithResult:(BOOL)result host:(SUHost *)host error:(NSError *)error delegate:delegate { if (result == YES) { - [self _mdimportBundle:hostBundle]; - if ([delegate respondsToSelector:@selector(installerFinishedForHostBundle:)]) - [delegate installerFinishedForHostBundle:hostBundle]; + [self _mdimportHost:host]; + if ([delegate respondsToSelector:@selector(installerFinishedForHost:)]) + [delegate installerFinishedForHost:host]; } else { - if ([delegate respondsToSelector:@selector(installerForHostBundle:failedWithError:)]) - [delegate installerForHostBundle:hostBundle failedWithError:error]; + if ([delegate respondsToSelector:@selector(installerForHost:failedWithError:)]) + [delegate installerForHost:host failedWithError:error]; } } diff --git a/SUPackageInstaller.h b/SUPackageInstaller.h index 94bb3ad07..65b1a8924 100644 --- a/SUPackageInstaller.h +++ b/SUPackageInstaller.h @@ -13,7 +13,7 @@ #import "SUPlainInstaller.h" @interface SUPackageInstaller : SUPlainInstaller { } -+ (void)installPath:(NSString *)path overHostBundle:(NSBundle *)bundle delegate:delegate; ++ (void)installPath:(NSString *)path overHost:(SUHost *)bundle delegate:delegate; @end #endif diff --git a/SUPackageInstaller.m b/SUPackageInstaller.m index f91a24cd1..057e150e5 100644 --- a/SUPackageInstaller.m +++ b/SUPackageInstaller.m @@ -11,7 +11,7 @@ @implementation SUPackageInstaller -+ (void)installPath:(NSString *)path overHostBundle:(NSBundle *)bundle delegate:delegate ++ (void)installPath:(NSString *)path overHost:(SUHost *)bundle delegate:delegate { NSError *error = nil; BOOL result = YES; @@ -26,7 +26,7 @@ + (void)installPath:(NSString *)path overHostBundle:(NSBundle *)bundle delegate: NSTask *installer = [NSTask launchedTaskWithLaunchPath:installerPath arguments:[NSArray arrayWithObjects:path, nil]]; [installer waitUntilExit]; // Known bug: if the installation fails or is canceled, Sparkle goes ahead and restarts, thinking everything is fine. - [self _finishInstallationWithResult:result hostBundle:bundle error:error delegate:delegate]; + [self _finishInstallationWithResult:result host:bundle error:error delegate:delegate]; } @end diff --git a/SUPlainInstaller.h b/SUPlainInstaller.h index 075ffe668..142d2e07f 100644 --- a/SUPlainInstaller.h +++ b/SUPlainInstaller.h @@ -12,7 +12,7 @@ #import "Sparkle.h" @interface SUPlainInstaller : SUInstaller { } -+ (void)performInstallationWithPath:(NSString *)path hostBundle:(NSBundle *)hostBundle delegate:delegate synchronously:(BOOL)synchronously; ++ (void)performInstallationWithPath:(NSString *)path host:(SUHost *)host delegate:delegate synchronously:(BOOL)synchronously; @end #endif diff --git a/SUPlainInstaller.m b/SUPlainInstaller.m index f876c19ef..940d55d2d 100644 --- a/SUPlainInstaller.m +++ b/SUPlainInstaller.m @@ -9,30 +9,30 @@ #import "SUPlainInstaller.h" extern NSString *SUInstallerPathKey; -extern NSString *SUInstallerHostBundleKey; +extern NSString *SUInstallerHostKey; extern NSString *SUInstallerDelegateKey; @implementation SUPlainInstaller -+ (void)installPath:(NSString *)path overHostBundle:(NSBundle *)bundle delegate:delegate ++ (void)installPath:(NSString *)path overHost:(SUHost *)bundle delegate:delegate { NSError *error; BOOL result = [[NSFileManager defaultManager] copyPathWithAuthentication:path overPath:[bundle bundlePath] error:&error]; - [self _finishInstallationWithResult:result hostBundle:bundle error:error delegate:delegate]; + [self _finishInstallationWithResult:result host:bundle error:error delegate:delegate]; } + (void)_performInstallationWithInfo:(NSDictionary *)info { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [self installPath:[info objectForKey:SUInstallerPathKey] overHostBundle:[info objectForKey:SUInstallerHostBundleKey] delegate:[info objectForKey:SUInstallerDelegateKey]]; + [self installPath:[info objectForKey:SUInstallerPathKey] overHost:[info objectForKey:SUInstallerHostKey] delegate:[info objectForKey:SUInstallerDelegateKey]]; [pool drain]; } -+ (void)performInstallationWithPath:(NSString *)path hostBundle:(NSBundle *)hostBundle delegate:delegate synchronously:(BOOL)synchronously; ++ (void)performInstallationWithPath:(NSString *)path host:(SUHost *)host delegate:delegate synchronously:(BOOL)synchronously; { - NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:path, SUInstallerPathKey, hostBundle, SUInstallerHostBundleKey, delegate, SUInstallerDelegateKey, nil]; + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:path, SUInstallerPathKey, host, SUInstallerHostKey, delegate, SUInstallerDelegateKey, nil]; if (synchronously) [self _performInstallationWithInfo:info]; else diff --git a/SUStatusController.h b/SUStatusController.h index e56dc8e78..c73ea5b82 100644 --- a/SUStatusController.h +++ b/SUStatusController.h @@ -11,15 +11,16 @@ #import "SUWindowController.h" +@class SUHost; @interface SUStatusController : SUWindowController { double progressValue, maxProgressValue; NSString *title, *statusText, *buttonTitle; IBOutlet NSButton *actionButton; IBOutlet NSProgressIndicator* progressBar; - NSBundle *hostBundle; + SUHost *host; } -- (id)initWithHostBundle:(NSBundle *)hostBundle; +- (id)initWithHost:(SUHost *)host; // Pass 0 for the max progress value to get an indeterminate progress bar. // Pass nil for the status text to not show it. diff --git a/SUStatusController.m b/SUStatusController.m index f344569bd..e6796e07d 100644 --- a/SUStatusController.m +++ b/SUStatusController.m @@ -11,12 +11,12 @@ @implementation SUStatusController -- (id)initWithHostBundle:(NSBundle *)hb +- (id)initWithHost:(SUHost *)hb { - self = [super initWithHostBundle:hb windowNibName:@"SUStatus"]; + self = [super initWithHost:hb windowNibName:@"SUStatus"]; if (self) { - hostBundle = [hb retain]; + host = [hb retain]; [self setShouldCascadeWindows:NO]; } return self; @@ -24,7 +24,7 @@ - (id)initWithHostBundle:(NSBundle *)hb - (void)dealloc { - [hostBundle release]; + [host release]; [title release]; [statusText release]; [buttonTitle release]; @@ -40,12 +40,12 @@ - (void)awakeFromNib - (NSString *)windowTitle { - return [NSString stringWithFormat:SULocalizedString(@"Updating %@", nil), [hostBundle name]]; + return [NSString stringWithFormat:SULocalizedString(@"Updating %@", nil), [host name]]; } - (NSImage *)applicationIcon { - return [hostBundle icon]; + return [host icon]; } - (void)beginActionWithTitle:(NSString *)aTitle maxProgressValue:(double)aMaxProgressValue statusText:(NSString *)aStatusText diff --git a/SUSystemProfiler.h b/SUSystemProfiler.h index 8ce9f5454..187de646d 100644 --- a/SUSystemProfiler.h +++ b/SUSystemProfiler.h @@ -11,7 +11,7 @@ @interface SUSystemProfiler : NSObject {} + (SUSystemProfiler *)sharedSystemProfiler; -- (NSMutableArray *)systemProfileArrayForHostBundle:(NSBundle *)hostBundle; +- (NSMutableArray *)systemProfileArrayForHost:(SUHost *)host; @end #endif diff --git a/SUSystemProfiler.m b/SUSystemProfiler.m index af109350b..0d36cbd76 100644 --- a/SUSystemProfiler.m +++ b/SUSystemProfiler.m @@ -27,7 +27,7 @@ - (NSDictionary *)modelTranslationTable return [[[NSDictionary alloc] initWithContentsOfFile:path] autorelease]; } -- (NSMutableArray *)systemProfileArrayForHostBundle:(NSBundle *)hostBundle +- (NSMutableArray *)systemProfileArrayForHost:(SUHost *)host { NSDictionary *modelTranslation = [self modelTranslationTable]; @@ -115,10 +115,10 @@ - (NSMutableArray *)systemProfileArrayForHostBundle:(NSBundle *)hostBundle [profileArray addObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"lang",@"Preferred Language", [languages objectAtIndex:0], [languages objectAtIndex:0],nil] forKeys:profileDictKeys]]; // Application sending the request - NSString *appName = [hostBundle name]; + NSString *appName = [host name]; if (appName) [profileArray addObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"appName",@"Application Name", appName, appName,nil] forKeys:profileDictKeys]]; - NSString *appVersion = [hostBundle version]; + NSString *appVersion = [host version]; if (appVersion) [profileArray addObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"appVersion",@"Application Version", appVersion, appVersion,nil] forKeys:profileDictKeys]]; diff --git a/SUUIBasedUpdateDriver.m b/SUUIBasedUpdateDriver.m index b7196dc0d..c712d223e 100644 --- a/SUUIBasedUpdateDriver.m +++ b/SUUIBasedUpdateDriver.m @@ -13,11 +13,11 @@ @implementation SUUIBasedUpdateDriver - (void)didFindValidUpdate { - updateAlert = [[SUUpdateAlert alloc] initWithAppcastItem:updateItem hostBundle:hostBundle]; + updateAlert = [[SUUpdateAlert alloc] initWithAppcastItem:updateItem host:host]; [updateAlert setDelegate:self]; // If the app is a menubar app or the like, we need to focus it first: - if ([[hostBundle objectForInfoDictionaryKey:@"LSUIElement"] doubleValue]) { [NSApp activateIgnoringOtherApps:YES]; } + if ([[host objectForInfoDictionaryKey:@"LSUIElement"] doubleValue]) { [NSApp activateIgnoringOtherApps:YES]; } // Only show the update alert if the app is active; otherwise, we'll wait until it is. if ([NSApp isActive]) @@ -28,10 +28,10 @@ - (void)didFindValidUpdate - (void)didNotFindUpdate { - if ([delegate respondsToSelector:@selector(didNotFindUpdateToHostBundle:)]) - [delegate didNotFindUpdateToHostBundle:hostBundle]; - NSAlert *alert = [NSAlert alertWithMessageText:SULocalizedString(@"You're up to date!", nil) defaultButton:SULocalizedString(@"OK", nil) alternateButton:nil otherButton:nil informativeTextWithFormat:SULocalizedString(@"%@ %@ is currently the newest version available.", nil), [hostBundle name], [hostBundle displayVersion]]; - [alert setIcon:[hostBundle icon]]; + if ([delegate respondsToSelector:@selector(didNotFindUpdateToHost:)]) + [delegate didNotFindUpdateToHost:host]; + NSAlert *alert = [NSAlert alertWithMessageText:SULocalizedString(@"You're up to date!", nil) defaultButton:SULocalizedString(@"OK", nil) alternateButton:nil otherButton:nil informativeTextWithFormat:SULocalizedString(@"%@ %@ is currently the newest version available.", nil), [host name], [host displayVersion]]; + [alert setIcon:[host icon]]; [alert runModal]; [self abortUpdate]; } @@ -45,13 +45,13 @@ - (void)applicationDidBecomeActive:(NSNotification *)aNotification - (void)updateAlert:(SUUpdateAlert *)alert finishedWithChoice:(SUUpdateAlertChoice)choice { [updateAlert release]; updateAlert = nil; - if ([delegate respondsToSelector:@selector(userChoseAction:forUpdate:toHostBundle:)]) - [delegate userChoseAction:choice forUpdate:updateItem toHostBundle:hostBundle]; - [[SUUserDefaults standardUserDefaults] setObject:nil forKey:SUSkippedVersionKey]; + if ([delegate respondsToSelector:@selector(userChoseAction:forUpdate:toHost:)]) + [delegate userChoseAction:choice forUpdate:updateItem toHost:host]; + [host setObject:nil forUserDefaultsKey:SUSkippedVersionKey]; switch (choice) { case SUInstallUpdateChoice: - statusController = [[SUStatusController alloc] initWithHostBundle:hostBundle]; + statusController = [[SUStatusController alloc] initWithHost:host]; [statusController beginActionWithTitle:SULocalizedString(@"Downloading update\u2026", @"Take care not to overflow the status window.") maxProgressValue:0 statusText:nil]; [statusController setButtonTitle:SULocalizedString(@"Cancel", nil) target:self action:@selector(cancelDownload:) isDefault:NO]; [statusController showWindow:self]; @@ -59,7 +59,7 @@ - (void)updateAlert:(SUUpdateAlert *)alert finishedWithChoice:(SUUpdateAlertChoi break; case SUSkipThisVersionChoice: - [[SUUserDefaults standardUserDefaults] setObject:[updateItem versionString] forKey:SUSkippedVersionKey]; + [host setObject:[updateItem versionString] forUserDefaultsKey:SUSkippedVersionKey]; case SURemindMeLaterChoice: [self abortUpdate]; break; @@ -138,7 +138,7 @@ - (void)installUpdate - (void)abortUpdateWithError:(NSError *)error { NSAlert *alert = [NSAlert alertWithMessageText:SULocalizedString(@"Update Error!", nil) defaultButton:SULocalizedString(@"Cancel Update", nil) alternateButton:nil otherButton:nil informativeTextWithFormat:[error localizedDescription]]; - [alert setIcon:[hostBundle icon]]; + [alert setIcon:[host icon]]; [alert runModal]; [super abortUpdateWithError:error]; } diff --git a/SUUpdateAlert.h b/SUUpdateAlert.h index a0288abad..1602b4a6a 100644 --- a/SUUpdateAlert.h +++ b/SUUpdateAlert.h @@ -18,10 +18,10 @@ typedef enum SUSkipThisVersionChoice } SUUpdateAlertChoice; -@class WebView, SUAppcastItem; +@class WebView, SUAppcastItem, SUHost; @interface SUUpdateAlert : SUWindowController { SUAppcastItem *updateItem; - NSBundle *hostBundle; + SUHost *host; id delegate; IBOutlet WebView *releaseNotesView; @@ -30,7 +30,7 @@ typedef enum BOOL webViewFinishedLoading; } -- (id)initWithAppcastItem:(SUAppcastItem *)item hostBundle:(NSBundle *)hostBundle; +- (id)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)host; - (void)setDelegate:delegate; - (IBAction)installUpdate:sender; diff --git a/SUUpdateAlert.m b/SUUpdateAlert.m index 5421bde4d..b3cc05f3a 100644 --- a/SUUpdateAlert.m +++ b/SUUpdateAlert.m @@ -13,12 +13,12 @@ @implementation SUUpdateAlert -- (id)initWithAppcastItem:(SUAppcastItem *)item hostBundle:(NSBundle *)hb +- (id)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)hb { - self = [super initWithHostBundle:hb windowNibName:@"SUUpdateAlert"]; + self = [super initWithHost:hb windowNibName:@"SUUpdateAlert"]; if (self) { - hostBundle = [hb retain]; + host = [hb retain]; updateItem = [item retain]; [self setShouldCascadeWindows:NO]; } @@ -28,7 +28,7 @@ - (id)initWithAppcastItem:(SUAppcastItem *)item hostBundle:(NSBundle *)hb - (void)dealloc { [updateItem release]; - [hostBundle release]; + [host release]; [super dealloc]; } @@ -85,7 +85,7 @@ - (void)displayReleaseNotes - (BOOL)showsReleaseNotes { - NSNumber *shouldShowReleaseNotes = [hostBundle objectForInfoDictionaryKey:SUShowReleaseNotesKey]; + NSNumber *shouldShowReleaseNotes = [host objectForInfoDictionaryKey:SUShowReleaseNotesKey]; if (shouldShowReleaseNotes == nil) return YES; // defaults to YES else @@ -94,11 +94,11 @@ - (BOOL)showsReleaseNotes - (BOOL)allowsAutomaticUpdates { - if (![[hostBundle objectForInfoDictionaryKey:SUExpectsDSASignatureKey] boolValue]) + if (![[host objectForInfoDictionaryKey:SUExpectsDSASignatureKey] boolValue]) return NO; // Automatic updating requires DSA-signed updates - if (![hostBundle objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey]) + if (![host objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey]) return YES; // defaults to YES - return [[hostBundle objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey] boolValue]; + return [[host objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey] boolValue]; } - (void)awakeFromNib @@ -140,17 +140,17 @@ - (BOOL)windowShouldClose:note - (NSImage *)applicationIcon { - return [hostBundle icon]; + return [host icon]; } - (NSString *)titleText { - return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ is available!", nil), [hostBundle name]]; + return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ is available!", nil), [host name]]; } - (NSString *)descriptionText { - return [NSString stringWithFormat:SULocalizedString(@"%@ %@ is now available\u2014you have %@. Would you like to download it now?", nil), [hostBundle name], [updateItem displayVersionString], [hostBundle displayVersion]]; + return [NSString stringWithFormat:SULocalizedString(@"%@ %@ is now available\u2014you have %@. Would you like to download it now?", nil), [host name], [updateItem displayVersionString], [host displayVersion]]; } - (void)webView:(WebView *)sender didFinishLoadForFrame:frame diff --git a/SUUpdateDriver.h b/SUUpdateDriver.h index face46e4e..7c9485d2d 100644 --- a/SUUpdateDriver.h +++ b/SUUpdateDriver.h @@ -13,12 +13,15 @@ extern NSString *SUUpdateDriverFinishedNotification; +@class SUHost; @interface SUUpdateDriver : NSObject { + SUHost *host; + BOOL finished; id delegate; } -- (void)checkForUpdatesAtURL:(NSURL *)appcastURL hostBundle:(NSBundle *)hb; +- (void)checkForUpdatesAtURL:(NSURL *)appcastURL host:(SUHost *)hb; - (void)abortUpdate; - (BOOL)finished; diff --git a/SUUpdateDriver.m b/SUUpdateDriver.m index 81e5ce9eb..d6c6d9e1b 100644 --- a/SUUpdateDriver.m +++ b/SUUpdateDriver.m @@ -11,9 +11,9 @@ NSString *SUUpdateDriverFinishedNotification = @"SUUpdateDriverFinished"; @implementation SUUpdateDriver -- (void)checkForUpdatesAtURL:(NSURL *)appcastURL hostBundle:(NSBundle *)hb +- (void)checkForUpdatesAtURL:(NSURL *)appcastURL host:(SUHost *)h { - [NSException raise:@"SUAbstractDriverError" format:@"Don't use SUUpdateDriver directly; use a subclass."]; + host = [h retain]; } - (void)abortUpdate diff --git a/SUUpdatePermissionPrompt.h b/SUUpdatePermissionPrompt.h index 24959da76..ad8e47d6d 100644 --- a/SUUpdatePermissionPrompt.h +++ b/SUUpdatePermissionPrompt.h @@ -16,15 +16,16 @@ typedef enum { SUDoNotAutomaticallyCheck } SUPermissionPromptResult; +@class SUHost; @interface SUUpdatePermissionPrompt : SUWindowController { - NSBundle *hostBundle; + SUHost *host; id delegate; IBOutlet NSTextField *descriptionTextField; IBOutlet NSView *moreInfoView; IBOutlet NSButton *moreInfoButton; BOOL isShowingMoreInfo, shouldSendProfile; } -+ (void)promptWithHostBundle:(NSBundle *)hb delegate:(id)d; ++ (void)promptWithHost:(SUHost *)hb delegate:(id)d; - (IBAction)toggleMoreInfo:(id)sender; - (IBAction)finishPrompt:(id)sender; @end diff --git a/SUUpdatePermissionPrompt.m b/SUUpdatePermissionPrompt.m index b954d4358..290ca197c 100644 --- a/SUUpdatePermissionPrompt.m +++ b/SUUpdatePermissionPrompt.m @@ -13,15 +13,15 @@ @implementation SUUpdatePermissionPrompt - (BOOL)shouldAskAboutProfile { - return [[hostBundle objectForInfoDictionaryKey:SUEnableSystemProfilingKey] boolValue]; + return [[host objectForInfoDictionaryKey:SUEnableSystemProfilingKey] boolValue]; } -- (id)initWithHostBundle:(NSBundle *)hb delegate:(id)d +- (id)initWithHost:(SUHost *)hb delegate:(id)d { - self = [super initWithHostBundle:hb windowNibName:@"SUUpdatePermissionPrompt"]; + self = [super initWithHost:hb windowNibName:@"SUUpdatePermissionPrompt"]; if (self) { - hostBundle = [hb retain]; + host = [hb retain]; delegate = [d retain]; isShowingMoreInfo = NO; shouldSendProfile = [self shouldAskAboutProfile]; @@ -30,9 +30,9 @@ - (id)initWithHostBundle:(NSBundle *)hb delegate:(id)d return self; } -+ (void)promptWithHostBundle:(NSBundle *)hb delegate:(id)d ++ (void)promptWithHost:(SUHost *)hb delegate:(id)d { - id prompt = [[[self class] alloc] initWithHostBundle:hb delegate:d]; + id prompt = [[[self class] alloc] initWithHost:hb delegate:d]; [NSApp runModalForWindow:[prompt window]]; } @@ -48,23 +48,23 @@ - (void)awakeFromNib - (void)dealloc { - [hostBundle release]; + [host release]; [super dealloc]; } - (NSImage *)icon { - return [hostBundle icon]; + return [host icon]; } - (NSString *)promptDescription { - return [NSString stringWithFormat:SULocalizedString(@"Should %1$@ automatically check for updates? You can always check for updates manually from the %1$@ menu.", nil), [hostBundle name]]; + return [NSString stringWithFormat:SULocalizedString(@"Should %1$@ automatically check for updates? You can always check for updates manually from the %1$@ menu.", nil), [host name]]; } - (NSArray *)systemProfileInformationArray { - return [[SUSystemProfiler sharedSystemProfiler] systemProfileArrayForHostBundle:hostBundle]; + return [[SUSystemProfiler sharedSystemProfiler] systemProfileArrayForHost:host]; } - (IBAction)toggleMoreInfo:(id)sender @@ -116,7 +116,7 @@ - (IBAction)finishPrompt:(id)sender { if (![delegate respondsToSelector:@selector(updatePermissionPromptFinishedWithResult:)]) [NSException raise:@"SUInvalidDelegate" format:@"SUUpdatePermissionPrompt's delegate (%@) doesn't respond to updatePermissionPromptFinishedWithResult:!", delegate]; - [[SUUserDefaults standardUserDefaults] setBool:shouldSendProfile forKey:SUSendProfileInfoKey]; + [host setBool:shouldSendProfile forUserDefaultsKey:SUSendProfileInfoKey]; [delegate updatePermissionPromptFinishedWithResult:([sender tag] == 1 ? SUAutomaticallyCheck : SUDoNotAutomaticallyCheck)]; [[self window] close]; [NSApp stopModal]; diff --git a/SUUpdater.h b/SUUpdater.h index 1d633531d..1381a497a 100644 --- a/SUUpdater.h +++ b/SUUpdater.h @@ -17,11 +17,12 @@ NSTimer *checkTimer; SUUpdateDriver *driver; - NSBundle *hostBundle; + SUHost *host; IBOutlet id delegate; } + (SUUpdater *)sharedUpdater; ++ (SUUpdater *)updaterForBundle:(NSBundle *)bundle; - (void)setDelegate:(id)delegate; @@ -37,10 +38,6 @@ // This forces an update to begin with a particular driver (see SU*UpdateDriver.h) - (void)checkForUpdatesWithDriver:(SUUpdateDriver *)driver; -// For non-.app updates: -// Call this when your bundle is loaded to tell Sparkle what to update. -- (void)setHostBundle:(NSBundle *)hostBundle; - // Call this to appropriately reschedule or cancel the update checking timer if preferences for time interval or automatic checks change. // If you're using a .app, this'll be picked up automatically via NSUserDefaultsController, but for non-.apps, there's no way to observe changes. - (void)updatePreferencesChanged; @@ -51,39 +48,39 @@ @interface NSObject (SUUpdaterDelegateInformalProtocol) // This method allows you to add extra parameters to the appcast URL, potentially based on whether or not // Sparkle will also be sending along the system profile. This method should return an array of dictionaries with the following keys: -- (NSArray *)feedParametersForHostBundle:(NSBundle *)bundle sendingSystemProfile:(BOOL)sendingProfile; +- (NSArray *)feedParametersForHost:(SUHost *)bundle sendingSystemProfile:(BOOL)sendingProfile; // Use this to override the default behavior for Sparkle prompting the user about automatic update checks. -- (BOOL)shouldPromptForPermissionToCheckForUpdatesToHostBundle:(NSBundle *)bundle; +- (BOOL)shouldPromptForPermissionToCheckForUpdatesToHost:(SUHost *)bundle; // Implement this if you want to do some special handling with the appcast once it finishes loading. -- (void)appcastDidFinishLoading:(SUAppcast *)appcast forHostBundle:(NSBundle *)bundle; +- (void)appcastDidFinishLoading:(SUAppcast *)appcast forHost:(SUHost *)bundle; // If you're using special logic or extensions in your appcast, implement this to use your own logic for finding // a valid update, if any, in the given appcast. -- (SUAppcastItem *)bestValidUpdateInAppcast:(SUAppcast *)appcast forHostBundle:(NSBundle *)bundle; +- (SUAppcastItem *)bestValidUpdateInAppcast:(SUAppcast *)appcast forHost:(SUHost *)bundle; // Sent when a valid update is found by the update driver. -- (void)didFindValidUpdate:(SUAppcastItem *)update toHostBundle:(NSBundle *)bundle; +- (void)didFindValidUpdate:(SUAppcastItem *)update toHost:(SUHost *)bundle; // Sent when a valid update is not found. -- (void)didNotFindUpdateToHostBundle:(NSBundle *)hb; +- (void)didNotFindUpdateToHost:(SUHost *)hb; // Sent when the user makes a choice in the update alert dialog (install now / remind me later / skip this version). -- (void)userChoseAction:(SUUpdateAlertChoice)action forUpdate:(SUAppcastItem *)update toHostBundle:(NSBundle *)bundle; +- (void)userChoseAction:(SUUpdateAlertChoice)action forUpdate:(SUAppcastItem *)update toHost:(SUHost *)bundle; // Sent immediately before installing the specified update. -- (void)updateWillInstall:(SUAppcastItem *)update toHostBundle:(NSBundle *)bundle; +- (void)updateWillInstall:(SUAppcastItem *)update toHost:(SUHost *)bundle; // Return YES to delay the relaunch until you do some processing; invoke the given NSInvocation to continue. -- (BOOL)shouldPostponeRelaunchForUpdate:(SUAppcastItem *)update toHostBundle:(NSBundle *)hostBundle untilInvoking:(NSInvocation *)invocation; +- (BOOL)shouldPostponeRelaunchForUpdate:(SUAppcastItem *)update toHost:(SUHost *)hostBundle untilInvoking:(NSInvocation *)invocation; // Called immediately before relaunching. - (void)updaterWillRelaunchApplication; // This method allows you to provide a custom version comparator. // If you don't implement this method or return nil, the standard version comparator will be used. -- (id )versionComparatorForHostBundle:(NSBundle *)hb; +- (id )versionComparatorForHost:(SUHost *)hb; @end diff --git a/SUUpdater.m b/SUUpdater.m index 4db0b093f..b9659b7a3 100644 --- a/SUUpdater.m +++ b/SUUpdater.m @@ -15,6 +15,10 @@ - (BOOL)automaticallyUpdates; - (BOOL)shouldScheduleUpdateCheck; - (void)scheduleNextUpdateCheck; - (NSTimeInterval)checkInterval; +- (void)registerAsObserver; +- (void)unregisterAsObserver; +- (void)updateDriverDidFinish:(NSNotification *)note; +- initForBundle:(NSBundle *)bundle; - (NSURL *)feedURL; @end @@ -22,71 +26,107 @@ @implementation SUUpdater #pragma mark Initialization -static SUUpdater *sharedUpdater = nil; +static NSMutableDictionary *sharedUpdaters = nil; +static NSString *SUUpdaterDefaultsObservationContext = @"SUUpdaterDefaultsObservationContext"; -// SUUpdater's a singleton now! And I'm enforcing it! -// This will probably break the world if you try to write a Sparkle-enabled plugin for a Sparkle-enabled app. + (SUUpdater *)sharedUpdater { - if (sharedUpdater == nil) - sharedUpdater = [[[self class] alloc] init]; - return sharedUpdater; + return [self updaterForBundle:[NSBundle mainBundle]]; } -- (id)init +// SUUpdater has a singleton for each bundle. We use the fact that NSBundle instances are also singletons, so we can use them as keys. If you don't trust that you can also use the identifier as key ++ (SUUpdater *)updaterForBundle:(NSBundle *)bundle +{ + if (bundle == nil) bundle = [NSBundle mainBundle]; + id updater = [sharedUpdaters objectForKey:[NSValue valueWithNonretainedObject:bundle]]; + if (updater == nil) + updater = [[[self class] alloc] initWithBundle:bundle]; + return updater; +} + +// This is the designated initializer for SUUpdater, important for subclasses +- initForBundle:(NSBundle *)bundle { self = [super init]; - if (sharedUpdater) + if (bundle == nil) bundle = [NSBundle mainBundle]; + id updater = [sharedUpdaters objectForKey:[NSValue valueWithNonretainedObject:bundle]]; + if (updater) { [self release]; - self = sharedUpdater; + self = [updater retain]; } - else if (self != nil) + else if (self) { - sharedUpdater = self; - [self setHostBundle:[NSBundle mainBundle]]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidFinishLaunching:) name:NSApplicationDidFinishLaunchingNotification object:NSApp]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(driverDidFinish:) name:SUUpdateDriverFinishedNotification object:nil]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:[@"values." stringByAppendingString:SUScheduledCheckIntervalKey] options:0 context:NULL]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:[@"values." stringByAppendingString:SUEnableAutomaticChecksKey] options:0 context:NULL]; + if (sharedUpdaters == nil) + sharedUpdaters = [[NSMutableDictionary alloc] init]; + [sharedUpdaters setObject:self forKey:[NSValue valueWithNonretainedObject:bundle]]; + host = [[SUHost alloc] initWithBundle:bundle]; + [self registerAsObserver]; } return self; } +// This will be used when the updater is instantiated in a nib such as MainMenu +- (id)init +{ + return [self initForBundle:[NSBundle mainBundle]]; +} + - (void)applicationDidFinishLaunching:(NSNotification *)note { - // If the user has been asked about automatic checks and said no, get out of here. - if ([[SUUserDefaults standardUserDefaults] objectForKey:SUEnableAutomaticChecksKey] && - [[SUUserDefaults standardUserDefaults] boolForKey:SUEnableAutomaticChecksKey] == NO) { return; } - - // Does the delegate want to take care of the logic for when we should ask permission to update? - if ([delegate respondsToSelector:@selector(shouldPromptForPermissionToCheckForUpdatesToHostBundle:)]) - { - if ([delegate shouldPromptForPermissionToCheckForUpdatesToHostBundle:hostBundle]) - [SUUpdatePermissionPrompt promptWithHostBundle:hostBundle delegate:self]; - } - // Has he been asked already? And don't ask if the host has a default value set in its Info.plist. - else if ([[SUUserDefaults standardUserDefaults] objectForKey:SUEnableAutomaticChecksKey] == nil && - [hostBundle objectForInfoDictionaryKey:SUEnableAutomaticChecksKey] == nil) - { - if ([[SUUserDefaults standardUserDefaults] objectForKey:SUEnableAutomaticChecksKeyOld]) - [[SUUserDefaults standardUserDefaults] setBool:[[SUUserDefaults standardUserDefaults] boolForKey:SUEnableAutomaticChecksKeyOld] forKey:SUEnableAutomaticChecksKey]; - // Now, we don't want to ask the user for permission to do a weird thing on the first launch. - // We wait until the second launch. - else if ([[SUUserDefaults standardUserDefaults] boolForKey:SUHasLaunchedBeforeKey] == NO) - [[SUUserDefaults standardUserDefaults] setBool:YES forKey:SUHasLaunchedBeforeKey]; - else - [SUUpdatePermissionPrompt promptWithHostBundle:hostBundle delegate:self]; + BOOL shouldPrompt = NO; + + // If the user has been asked about automatic checks, don't bother prompting + if ([host objectForUserDefaultsKey:SUEnableAutomaticChecksKey]) + { + shouldPrompt = NO; + } + // Does the delegate want to take care of the logic for when we should ask permission to update? + else if ([delegate respondsToSelector:@selector(shouldPromptForPermissionToCheckForUpdatesToHostBundle:)]) + { + shouldPrompt = [delegate shouldPromptForPermissionToCheckForUpdatesToHost:host]; + } + // Has he been asked already? And don't ask if the host has a default value set in its Info.plist. + else if ([host objectForUserDefaultsKey:SUEnableAutomaticChecksKey] == nil && + [host objectForInfoDictionaryKey:SUEnableAutomaticChecksKey] == nil) + { + if ([host objectForUserDefaultsKey:SUEnableAutomaticChecksKeyOld]) + [host setBool:[host boolForUserDefaultsKey:SUEnableAutomaticChecksKeyOld] forUserDefaultsKey:SUEnableAutomaticChecksKey]; + // Now, we don't want to ask the user for permission to do a weird thing on the first launch. + // We wait until the second launch. + else if ([host boolForUserDefaultsKey:SUHasLaunchedBeforeKey] == NO) + [host setBool:YES forUserDefaultsKey:SUHasLaunchedBeforeKey]; + else + shouldPrompt = YES; + } + + if (shouldPrompt) + { + [SUUpdatePermissionPrompt promptWithHost:host delegate:self]; + // We start the update checks and register as observer for changes after the prompt finishes } - - // We check if the user's said they want updates, or they haven't said anything, and the default is set to checking. - [self scheduleNextUpdateCheck]; + else + { + // We check if the user's said they want updates, or they haven't said anything, and the default is set to checking. + [self scheduleNextUpdateCheck]; + } } - (void)updatePermissionPromptFinishedWithResult:(SUPermissionPromptResult)result { - [[SUUserDefaults standardUserDefaults] setBool:(result == SUAutomaticallyCheck) forKey:SUEnableAutomaticChecksKey]; - [self scheduleNextUpdateCheck]; + BOOL automaticallyCheck = (result == SUAutomaticallyCheck); + [host setBool:automaticallyCheck forUserDefaultsKey:SUEnableAutomaticChecksKey]; + // Schedule checks, but make sure we ignore the delayed call from KVO + [self updatePreferencesChanged]; +} + +- (void)updateDriverDidFinish:(NSNotification *)note +{ + if ([note object] == driver && [driver finished]) + { + [driver release]; driver = nil; + [self scheduleNextUpdateCheck]; + } } - (void)scheduleNextUpdateCheck @@ -99,7 +139,7 @@ - (void)scheduleNextUpdateCheck if (![self shouldScheduleUpdateCheck]) return; // How long has it been since last we checked for an update? - NSDate *lastCheckDate = [[SUUserDefaults standardUserDefaults] objectForKey:SULastCheckTimeKey]; + NSDate *lastCheckDate = [host objectForUserDefaultsKey:SULastCheckTimeKey]; if (!lastCheckDate) { lastCheckDate = [NSDate distantPast]; } NSTimeInterval intervalSinceCheck = [[NSDate date] timeIntervalSinceDate:lastCheckDate]; @@ -127,37 +167,70 @@ - (void)checkForUpdatesWithDriver:(SUUpdateDriver *)d if ([self updateInProgress]) { return; } if (checkTimer) { [checkTimer invalidate]; checkTimer = nil; } + // A value in the user defaults overrides one in the Info.plist (so preferences panels can be created wherein users choose between beta / release feeds). + NSString *appcastString = [host objectForUserDefaultsKey:SUFeedURLKey]; + if (!appcastString) + appcastString = [host objectForInfoDictionaryKey:SUFeedURLKey]; + if (!appcastString) + [NSException raise:@"SUNoFeedURL" format:@"You must specify the URL of the appcast as the SUFeedURLKey in either the Info.plist or the user defaults!"]; + driver = [d retain]; if ([driver delegate] == nil) { [driver setDelegate:delegate]; } - [driver checkForUpdatesAtURL:[self feedURL] hostBundle:hostBundle]; + [driver checkForUpdatesAtURL:[self feedURL] host:host]; } -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +- (void)registerAsObserver { - if (object == [NSUserDefaultsController sharedUserDefaultsController] && ([keyPath hasSuffix:SUScheduledCheckIntervalKey] || [keyPath hasSuffix:SUEnableAutomaticChecksKey])) - { - [self updatePreferencesChanged]; - } - else + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidFinishLaunching:) name:NSApplicationDidFinishLaunchingNotification object:NSApp]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateDriverDidFinish:) name:SUUpdateDriverFinishedNotification object:nil]; + // No sense observing the shared NSUserDefaultsController when we're not updating the main bundle. + if ([host bundle] != [NSBundle mainBundle]) return; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:[@"values." stringByAppendingString:SUScheduledCheckIntervalKey] options:0 context:SUUpdaterDefaultsObservationContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:[@"values." stringByAppendingString:SUEnableAutomaticChecksKey] options:0 context:SUUpdaterDefaultsObservationContext]; +} + +- (void)unregisterAsObserver +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + // Removing self as a KVO observer if no observer was registered leads to an NSException. But we don't care. + @try { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:[@"values." stringByAppendingString:SUScheduledCheckIntervalKey]]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:[@"values." stringByAppendingString:SUEnableAutomaticChecksKey]]; } + @catch (NSException *e) { } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if (context == SUUpdaterDefaultsObservationContext) + { + // Allow a small delay, because perhaps the user or developer wants to change both preferences. This allows the developer to interpret a zero check interval as a sign to disable automatic checking. + // Or we may get this from the developer and from our own KVO observation, this will effectively coalesce them. + [[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(updatePreferencesChanged) object:nil]; + [self performSelector:@selector(updatePreferencesChanged) withObject:nil afterDelay:15]; + [self updatePreferencesChanged]; + } + else + { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } } - (void)updatePreferencesChanged { - [self scheduleNextUpdateCheck]; + [[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(updatePreferencesChanged) object:nil]; + [self scheduleNextUpdateCheck]; } - (BOOL)shouldScheduleUpdateCheck { // Breaking this down for readability: // If the user says he wants automatic update checks, let's do it. - if ([[SUUserDefaults standardUserDefaults] boolForKey:SUEnableAutomaticChecksKey] == YES) + if ([host boolForUserDefaultsKey:SUEnableAutomaticChecksKey] == YES) return YES; // If the user hasn't said anything, but the developer says we should do it, let's do it. - if ([[SUUserDefaults standardUserDefaults] objectForKey:SUEnableAutomaticChecksKey] == nil && - [[hostBundle objectForInfoDictionaryKey:SUEnableAutomaticChecksKey] boolValue] == YES) + if ([host objectForUserDefaultsKey:SUEnableAutomaticChecksKey] == nil && [host boolForInfoDictionaryKey:SUEnableAutomaticChecksKey] == YES) return YES; return NO; // Otherwise, don't bother. } @@ -165,16 +238,15 @@ - (BOOL)shouldScheduleUpdateCheck - (BOOL)automaticallyUpdates { // If the SUAllowsAutomaticUpdatesKey exists and is set to NO, return NO. - if ([hostBundle objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey] && - [[hostBundle objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey] boolValue] == NO) + if ([host objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey] && [host boolForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey] == NO) return NO; // If we're not using DSA signatures, we aren't going to trust any updates automatically. - if ([[hostBundle objectForInfoDictionaryKey:SUExpectsDSASignatureKey] boolValue] != YES) + if ([host boolForInfoDictionaryKey:SUExpectsDSASignatureKey] != YES) return NO; // If there's no setting, or it's set to no, we're not automatically updating. - if ([[SUUserDefaults standardUserDefaults] boolForKey:SUAutomaticallyUpdateKey] != YES) + if ([host boolForUserDefaultsKey:SUAutomaticallyUpdateKey] != YES) return NO; return YES; // Otherwise, we're good to go. @@ -183,9 +255,9 @@ - (BOOL)automaticallyUpdates - (NSURL *)_baseFeedURL { // A value in the user defaults overrides one in the Info.plist (so preferences panels can be created wherein users choose between beta / release feeds). - NSString *appcastString = [[SUUserDefaults standardUserDefaults] objectForKey:SUFeedURLKey]; + NSString *appcastString = [host objectForUserDefaultsKey:SUFeedURLKey]; if (!appcastString) - appcastString = [hostBundle objectForInfoDictionaryKey:SUFeedURLKey]; + appcastString = [host objectForInfoDictionaryKey:SUFeedURLKey]; if (!appcastString) // Can't find an appcast string! [NSException raise:@"SUNoFeedURL" format:@"You must specify the URL of the appcast as the SUFeedURLKey in either the Info.plist or the user defaults!"]; NSCharacterSet* quoteSet = [NSCharacterSet characterSetWithCharactersInString: @"\"\'"]; // Some feed publishers add quotes; strip 'em. @@ -197,12 +269,12 @@ - (NSURL *)feedURL NSURL *baseFeedURL = [self _baseFeedURL]; // Determine all the parameters we're attaching to the base feed URL. - BOOL sendingSystemProfile = ([[SUUserDefaults standardUserDefaults] boolForKey:SUSendProfileInfoKey] == YES); + BOOL sendingSystemProfile = ([host boolForUserDefaultsKey:SUSendProfileInfoKey] == YES); NSArray *parameters = [NSArray array]; if ([delegate respondsToSelector:@selector(feedParametersForHostBundle:sendingSystemProfile:)]) - parameters = [parameters arrayByAddingObjectsFromArray:[delegate feedParametersForHostBundle:hostBundle sendingSystemProfile:sendingSystemProfile]]; + parameters = [parameters arrayByAddingObjectsFromArray:[delegate feedParametersForHost:host sendingSystemProfile:sendingSystemProfile]]; if (sendingSystemProfile) - parameters = [parameters arrayByAddingObjectsFromArray:[hostBundle systemProfile]]; + parameters = [parameters arrayByAddingObjectsFromArray:[host systemProfile]]; if (parameters == nil || [parameters count] == 0) { return baseFeedURL; } // Build up the parameterized URL. @@ -222,10 +294,10 @@ - (NSTimeInterval)checkInterval { NSTimeInterval checkInterval = 0; // Find the stored check interval. User defaults override Info.plist. - if ([[SUUserDefaults standardUserDefaults] objectForKey:SUScheduledCheckIntervalKey]) - checkInterval = [[[SUUserDefaults standardUserDefaults] objectForKey:SUScheduledCheckIntervalKey] doubleValue]; - else if ([hostBundle objectForInfoDictionaryKey:SUScheduledCheckIntervalKey]) - checkInterval = [[hostBundle objectForInfoDictionaryKey:SUScheduledCheckIntervalKey] doubleValue]; + if ([host objectForUserDefaultsKey:SUScheduledCheckIntervalKey]) + checkInterval = [[host objectForUserDefaultsKey:SUScheduledCheckIntervalKey] doubleValue]; + else if ([host objectForInfoDictionaryKey:SUScheduledCheckIntervalKey]) + checkInterval = [[host objectForInfoDictionaryKey:SUScheduledCheckIntervalKey] doubleValue]; if (checkInterval < SU_MIN_CHECK_INTERVAL) // This can also mean one that isn't set. checkInterval = SU_DEFAULT_CHECK_INTERVAL; @@ -234,9 +306,9 @@ - (NSTimeInterval)checkInterval - (void)dealloc { - [hostBundle release]; + [self unregisterAsObserver]; + [host release]; if (checkTimer) { [checkTimer invalidate]; } - [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } @@ -252,25 +324,25 @@ - (void)setDelegate:aDelegate delegate = aDelegate; } -- (void)setHostBundle:(NSBundle *)hb -{ - if (hostBundle == hb) return; - [hostBundle release]; - hostBundle = [hb retain]; - [[SUUserDefaults standardUserDefaults] setIdentifier:[hostBundle bundleIdentifier]]; -} - - (BOOL)updateInProgress { return driver && ([driver finished] == NO); } -- (void)driverDidFinish:(NSNotification *)notification +// Redirect driver delegate methods to our delegate +/* +- (void)appcastDidFinishLoading:(SUAppcast *)appcast forHost:(SUHost *)bundle { - if ([notification object] != driver) return; - [driver release]; - driver = nil; - [self scheduleNextUpdateCheck]; + if ([delegate respondsToSelector:@selector(appcastDidFinishLoading:forHost:)]) + [delegate appcastDidFinishLoading:ac forHost:bundle]; } +- (SUAppcastItem *)bestValidUpdateInAppcast:(SUAppcast *)appcast forHost:(SUHost *)bundle +{ + if ([delegate respondsToSelector:@selector(bestValidUpdateInAppcast:forHost:)]) // Does the delegate want to handle it? + return [delegate bestValidUpdateInAppcast:ac forHost:bundle]; + else + return nil; +}*/ + @end diff --git a/SUUserDefaults.h b/SUUserDefaults.h deleted file mode 100644 index ba2eedb21..000000000 --- a/SUUserDefaults.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// SUUserDefaults.h -// Sparkle -// -// Created by Andy Matuschak on 12/21/07. -// Copyright 2007 Andy Matuschak. All rights reserved. -// - -#ifndef SUUSERDEFAULTS_H -#define SUUSERDEFAULTS_H - -/*! - @class - @abstract A substitute for NSUserDefaults that will work with arbitrary bundle identifiers. - @discussion Make sure you call -setIdentifier: before using SUUserDefaults. The other methods in this class work just like those in NSUserDefaults. -*/ - -@interface SUUserDefaults : NSObject { - NSString *identifier; -} - -/*! - @method - @abstract Returns a singleton instance of the user defaults class. -*/ -+ (SUUserDefaults *)standardUserDefaults; - -/*! - @method - @abstract Sets which bundle identifier to use when setting and retrieving defaults. - @discussion It is imperative that you set the identifier through this method before trying to set or retrieve defaults. -*/ -- (void)setIdentifier:(NSString *)identifier; - -- (id)objectForKey:(NSString *)defaultName; -- (void)setObject:(id)value forKey:(NSString *)defaultName; -- (BOOL)boolForKey:(NSString *)defaultName; -- (void)setBool:(BOOL)value forKey:(NSString *)defaultName; -@end - -#endif diff --git a/SUUserDefaults.m b/SUUserDefaults.m deleted file mode 100644 index 26f995ad4..000000000 --- a/SUUserDefaults.m +++ /dev/null @@ -1,97 +0,0 @@ -// -// SUUserDefaults.m -// Sparkle -// -// Created by Andy Matuschak on 12/21/07. -// Copyright 2007 Andy Matuschak. All rights reserved. -// - -#import "Sparkle.h" -#import "SUUserDefaults.h" - -@implementation SUUserDefaults - -+ (SUUserDefaults *)standardUserDefaults -{ - static SUUserDefaults *standardUserDefaults = nil; - if (standardUserDefaults == nil) - standardUserDefaults = [[SUUserDefaults alloc] init]; - return standardUserDefaults; -} - -- (void)dealloc -{ - [identifier release]; - [super dealloc]; -} - -- (void)setIdentifier:(NSString *)anIdentifier -{ - if (identifier != anIdentifier) - { - [identifier release]; - identifier = [anIdentifier copy]; - } -} - -- (void)verifyIdentifier -{ - if (identifier == nil) - [NSException raise:@"SUUserDefaultsMissingIdentifier" format:@"You must set the SUUserDefaults identifier before using it."]; -} - -- (id)objectForKey:(NSString *)defaultName -{ - [self verifyIdentifier]; - CFPropertyListRef obj = CFPreferencesCopyAppValue((CFStringRef)defaultName, (CFStringRef)identifier); - // Under Tiger, CFPreferencesCopyAppValue doesn't get values from NSRegistrationDomain, so anything - // passed into -[NSUserDefaults registerDefaults:] is ignored. The following line falls - // back to using NSUserDefaults, but only if the host bundle is the main bundle, and no value - // is found elsewhere. - if (obj == NULL && [identifier isEqualToString:[[NSBundle mainBundle] bundleIdentifier]]) - obj = [[NSUserDefaults standardUserDefaults] objectForKey:defaultName]; - id result = nil; -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 - result = [NSMakeCollectable(obj) autorelease]; -#endif - return result ?: (id)obj; -} - -- (void)setObject:(id)value forKey:(NSString *)defaultName; -{ - [self verifyIdentifier]; - CFPreferencesSetValue((CFStringRef)defaultName, value, (CFStringRef)identifier, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); - CFPreferencesSynchronize((CFStringRef)identifier, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); - // If anything's bound to this through an NSUserDefaultsController, it won't know that anything's changed. - // We can't get an NSUserDefaults object for anything other than the standard one for the app, so this won't work for bundles. - // But it's the best we can do: this will make NSUserDefaultsControllers know about the changes that have been made. - [[NSUserDefaults standardUserDefaults] synchronize]; -} - -- (BOOL)boolForKey:(NSString *)defaultName -{ - BOOL value; - [self verifyIdentifier]; - CFPropertyListRef plr = CFPreferencesCopyAppValue((CFStringRef)defaultName, (CFStringRef)identifier); - if (plr == NULL) - value = NO; - else { - value = (BOOL)CFBooleanGetValue((CFBooleanRef)plr); - CFRelease(plr); - } - - return value; -} - -- (void)setBool:(BOOL)value forKey:(NSString *)defaultName -{ - [self verifyIdentifier]; - CFPreferencesSetValue((CFStringRef)defaultName, (CFBooleanRef)[NSNumber numberWithBool:value], (CFStringRef)identifier, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); - CFPreferencesSynchronize((CFStringRef)identifier, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); - // If anything's bound to this through an NSUserDefaultsController, it won't know that anything's changed. - // We can't get an NSUserDefaults object for anything other than the standard one for the app, so this won't work for bundles. - // But it's the best we can do: this will make NSUserDefaultsControllers know about the changes that have been made. - [[NSUserDefaults standardUserDefaults] synchronize]; -} - -@end diff --git a/SUUserInitiatedUpdateDriver.m b/SUUserInitiatedUpdateDriver.m index 94fa66a88..88c3bfce5 100644 --- a/SUUserInitiatedUpdateDriver.m +++ b/SUUserInitiatedUpdateDriver.m @@ -11,10 +11,10 @@ @implementation SUUserInitiatedUpdateDriver -- (void)checkForUpdatesAtURL:(NSURL *)appcastURL hostBundle:(NSBundle *)hb +- (void)checkForUpdatesAtURL:(NSURL *)appcastURL host:(SUHost *)hb { - [super checkForUpdatesAtURL:appcastURL hostBundle:hb]; - checkingController = [[SUStatusController alloc] initWithHostBundle:hb]; + [super checkForUpdatesAtURL:appcastURL host:hb]; + checkingController = [[SUStatusController alloc] initWithHost:hb]; [[checkingController window] center]; // Force the checking controller to load its window. [checkingController beginActionWithTitle:SULocalizedString(@"Checking for updates\u2026", nil) maxProgressValue:0 statusText:nil]; [checkingController setButtonTitle:SULocalizedString(@"Cancel", nil) target:self action:@selector(cancelCheckForUpdates:) isDefault:NO]; diff --git a/SUWindowController.h b/SUWindowController.h index 8b50a64c0..2962429dc 100644 --- a/SUWindowController.h +++ b/SUWindowController.h @@ -11,9 +11,10 @@ #import +@class SUHost; @interface SUWindowController : NSWindowController { } // We use this instead of plain old NSWindowController initWithWindowNibName so that we'll be able to find the right path when running in a bundle loaded from another app. -- (id)initWithHostBundle:(NSBundle *)hb windowNibName:(NSString *)nibName; +- (id)initWithHost:(SUHost *)hb windowNibName:(NSString *)nibName; @end #endif diff --git a/SUWindowController.m b/SUWindowController.m index f73ef80d9..7e48b52eb 100644 --- a/SUWindowController.m +++ b/SUWindowController.m @@ -10,7 +10,7 @@ @implementation SUWindowController -- (id)initWithHostBundle:(NSBundle *)hb windowNibName:(NSString *)nibName +- (id)initWithHost:(SUHost *)hb windowNibName:(NSString *)nibName { NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:nibName ofType:@"nib"]; if (path == nil) // Slight hack to resolve issues with running Sparkle in debug configurations. diff --git a/Sparkle.h b/Sparkle.h index db304b094..4d0b1091b 100644 --- a/Sparkle.h +++ b/Sparkle.h @@ -25,7 +25,6 @@ // This list should include the shared headers. It doesn't matter if some of them aren't shared (unless // there are name-space collisions) so we can list all of them to start with: -#import "NSBundle+SUAdditions.h" #import "NSFileManager+Aliases.h" #import "NSFileManager+Authentication.h" #import "NSFileManager+Verification.h" @@ -40,6 +39,7 @@ #import "SUAutomaticUpdateDriver.h" #import "SUBasicUpdateDriver.h" #import "SUConstants.h" +#import "SUHost.h" #import "SUInstaller.h" #import "SUProbingUpdateDriver.h" #import "SUScheduledUpdateDriver.h" @@ -53,7 +53,6 @@ #import "SUUpdater.h" #import "SUUpdatePermissionPrompt.h" #import "SUUserInitiatedUpdateDriver.h" -#import "SUUserDefaults.h" #import "SUVersionComparisonProtocol.h" #import "SUWindowController.h" diff --git a/Sparkle.xcodeproj/project.pbxproj b/Sparkle.xcodeproj/project.pbxproj index 0e874f89d..246f8ce47 100644 --- a/Sparkle.xcodeproj/project.pbxproj +++ b/Sparkle.xcodeproj/project.pbxproj @@ -55,13 +55,9 @@ 618FA5230DAE8E8A0026945C /* SUPackageInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 618FA5210DAE8E8A0026945C /* SUPackageInstaller.m */; }; 6196CFF909C72148000DC222 /* SUStatusController.h in Headers */ = {isa = PBXBuildFile; fileRef = 6196CFE309C71ADE000DC222 /* SUStatusController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6196CFFA09C72149000DC222 /* SUStatusController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6196CFE409C71ADE000DC222 /* SUStatusController.m */; }; - 61A225930D1C3ADD00430CCD /* NSBundle+SUAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 61A225910D1C3ADD00430CCD /* NSBundle+SUAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 61A225940D1C3ADD00430CCD /* NSBundle+SUAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 61A225920D1C3ADD00430CCD /* NSBundle+SUAdditions.m */; }; 61A2259E0D1C495D00430CCD /* SUVersionComparisonProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 61A2259C0D1C495D00430CCD /* SUVersionComparisonProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 61A225A40D1C4AC000430CCD /* SUStandardVersionComparator.h in Headers */ = {isa = PBXBuildFile; fileRef = 61A225A20D1C4AC000430CCD /* SUStandardVersionComparator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 61A225A50D1C4AC000430CCD /* SUStandardVersionComparator.m in Sources */ = {isa = PBXBuildFile; fileRef = 61A225A30D1C4AC000430CCD /* SUStandardVersionComparator.m */; }; - 61A2262B0D1C58FD00430CCD /* SUUserDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = 61A226290D1C58FD00430CCD /* SUUserDefaults.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 61A2262C0D1C58FD00430CCD /* SUUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 61A2262A0D1C58FD00430CCD /* SUUserDefaults.m */; }; 61A2279C0D1CEE7600430CCD /* SUSystemProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 61A2279A0D1CEE7600430CCD /* SUSystemProfiler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 61A2279D0D1CEE7600430CCD /* SUSystemProfiler.m in Sources */ = {isa = PBXBuildFile; fileRef = 61A2279B0D1CEE7600430CCD /* SUSystemProfiler.m */; }; 61A354550DF113C70076ECB1 /* SUUserInitiatedUpdateDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 61A354530DF113C70076ECB1 /* SUUserInitiatedUpdateDriver.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -95,6 +91,8 @@ 61C1F2790DA066C2007ACE13 /* NSFileManager+ExtendedAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 61C1F2770DA066C2007ACE13 /* NSFileManager+ExtendedAttributes.m */; }; 61C46F340D9C54F300B06326 /* SUUpdatePermissionPrompt.nib in Resources */ = {isa = PBXBuildFile; fileRef = 61C46F330D9C54F300B06326 /* SUUpdatePermissionPrompt.nib */; }; 61D85D6D0E10B2ED00F9B4A9 /* SUPipedUnarchiver.m in Sources */ = {isa = PBXBuildFile; fileRef = 6129C0B90E0B79810062CE76 /* SUPipedUnarchiver.m */; }; + 61EF67560E25B58D00F754E0 /* SUHost.m in Sources */ = {isa = PBXBuildFile; fileRef = 61EF67550E25B58D00F754E0 /* SUHost.m */; }; + 61EF67590E25C5B400F754E0 /* SUHost.h in Headers */ = {isa = PBXBuildFile; fileRef = 61EF67580E25C5B400F754E0 /* SUHost.h */; }; 61F83F720DBFE140006FDD30 /* SUBasicUpdateDriver.m in Sources */ = {isa = PBXBuildFile; fileRef = 61F83F700DBFE137006FDD30 /* SUBasicUpdateDriver.m */; }; 61F83F740DBFE141006FDD30 /* SUBasicUpdateDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 61F83F6F0DBFE137006FDD30 /* SUBasicUpdateDriver.h */; settings = {ATTRIBUTES = (Public, ); }; }; DAAEFC9B0DA5722F0051E0D0 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D6A5FE840307C02AAC07 /* AppKit.framework */; }; @@ -206,13 +204,9 @@ 619B17210E1E9D0800E72754 /* de */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = de; path = de.lproj/SUAutomaticUpdateAlert.nib; sourceTree = ""; }; 619B17220E1E9D0800E72754 /* de */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = de; path = de.lproj/SUUpdateAlert.nib; sourceTree = ""; }; 619B17230E1E9D0800E72754 /* de */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = de; path = de.lproj/SUUpdatePermissionPrompt.nib; sourceTree = ""; }; - 61A225910D1C3ADD00430CCD /* NSBundle+SUAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+SUAdditions.h"; sourceTree = ""; }; - 61A225920D1C3ADD00430CCD /* NSBundle+SUAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+SUAdditions.m"; sourceTree = ""; }; 61A2259C0D1C495D00430CCD /* SUVersionComparisonProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUVersionComparisonProtocol.h; sourceTree = ""; }; 61A225A20D1C4AC000430CCD /* SUStandardVersionComparator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUStandardVersionComparator.h; sourceTree = ""; }; 61A225A30D1C4AC000430CCD /* SUStandardVersionComparator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUStandardVersionComparator.m; sourceTree = ""; }; - 61A226290D1C58FD00430CCD /* SUUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUUserDefaults.h; sourceTree = ""; }; - 61A2262A0D1C58FD00430CCD /* SUUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUUserDefaults.m; sourceTree = ""; }; 61A2279A0D1CEE7600430CCD /* SUSystemProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUSystemProfiler.h; sourceTree = ""; }; 61A2279B0D1CEE7600430CCD /* SUSystemProfiler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUSystemProfiler.m; sourceTree = ""; }; 61A354530DF113C70076ECB1 /* SUUserInitiatedUpdateDriver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUUserInitiatedUpdateDriver.h; sourceTree = ""; }; @@ -249,6 +243,8 @@ 61C1F2760DA066C2007ACE13 /* NSFileManager+ExtendedAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFileManager+ExtendedAttributes.h"; sourceTree = ""; }; 61C1F2770DA066C2007ACE13 /* NSFileManager+ExtendedAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFileManager+ExtendedAttributes.m"; sourceTree = ""; }; 61C46F350D9C54F300B06326 /* en */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = en; path = en.lproj/SUUpdatePermissionPrompt.nib; sourceTree = ""; }; + 61EF67550E25B58D00F754E0 /* SUHost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUHost.m; sourceTree = ""; }; + 61EF67580E25C5B400F754E0 /* SUHost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUHost.h; sourceTree = ""; }; 61F3651A0E18987B007ECA02 /* es */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = es; path = es.lproj/SUUpdatePermissionPrompt.nib; sourceTree = ""; }; 61F3652A0E189883007ECA02 /* es */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = es; path = es.lproj/SUUpdateAlert.nib; sourceTree = ""; }; 61F3652B0E189883007ECA02 /* es */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = es; path = es.lproj/SUAutomaticUpdateAlert.nib; sourceTree = ""; }; @@ -473,8 +469,8 @@ 61B5F8F309C4CE5900B25A18 /* Other Sources */ = { isa = PBXGroup; children = ( - 61A225910D1C3ADD00430CCD /* NSBundle+SUAdditions.h */, - 61A225920D1C3ADD00430CCD /* NSBundle+SUAdditions.m */, + 61EF67580E25C5B400F754E0 /* SUHost.h */, + 61EF67550E25B58D00F754E0 /* SUHost.m */, 611779570D1111C400749C97 /* NSWorkspace_RBAdditions.h */, 611779560D1111C400749C97 /* NSWorkspace_RBAdditions.m */, 610134750DD252E60049ACDF /* NSWorkspace+SystemVersion.h */, @@ -482,8 +478,6 @@ 61299B3509CB04E000B7442F /* Sparkle.h */, 61299A5B09CA6D4500B7442F /* SUConstants.h */, 61299A5F09CA6EB100B7442F /* SUConstants.m */, - 61A226290D1C58FD00430CCD /* SUUserDefaults.h */, - 61A2262A0D1C58FD00430CCD /* SUUserDefaults.m */, ); includeInIndex = 1; name = "Other Sources"; @@ -563,10 +557,8 @@ 61299B3609CB04E000B7442F /* Sparkle.h in Headers */, 6120721209CC5C4B007FE0F6 /* SUAutomaticUpdateAlert.h in Headers */, 611779590D1111C900749C97 /* NSWorkspace_RBAdditions.h in Headers */, - 61A225930D1C3ADD00430CCD /* NSBundle+SUAdditions.h in Headers */, 61A2259E0D1C495D00430CCD /* SUVersionComparisonProtocol.h in Headers */, 61A225A40D1C4AC000430CCD /* SUStandardVersionComparator.h in Headers */, - 61A2262B0D1C58FD00430CCD /* SUUserDefaults.h in Headers */, 61A2279C0D1CEE7600430CCD /* SUSystemProfiler.h in Headers */, 6160E7E10D3B4A8800E9CD71 /* NTSynchronousTask.h in Headers */, 612DCBAF0D488BC60015DBEA /* SUUpdatePermissionPrompt.h in Headers */, @@ -588,6 +580,7 @@ 6102FE460E077FCE00F85D09 /* SUPipedUnarchiver.h in Headers */, 6102FE4A0E07803800F85D09 /* SUDiskImageUnarchiver.h in Headers */, 6102FE5B0E08C7EC00F85D09 /* SUUnarchiver_Private.h in Headers */, + 61EF67590E25C5B400F754E0 /* SUHost.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -824,9 +817,7 @@ 6120721309CC5C4B007FE0F6 /* SUAutomaticUpdateAlert.m in Sources */, 610EC1E00CF3A5FE00AE239E /* NTSynchronousTask.m in Sources */, 6117795A0D1111C900749C97 /* NSWorkspace_RBAdditions.m in Sources */, - 61A225940D1C3ADD00430CCD /* NSBundle+SUAdditions.m in Sources */, 61A225A50D1C4AC000430CCD /* SUStandardVersionComparator.m in Sources */, - 61A2262C0D1C58FD00430CCD /* SUUserDefaults.m in Sources */, 61A2279D0D1CEE7600430CCD /* SUSystemProfiler.m in Sources */, 612DCBB00D488BC60015DBEA /* SUUpdatePermissionPrompt.m in Sources */, 6171D9080D57B81800BFE886 /* NSFileManager+Aliases.m in Sources */, @@ -846,6 +837,7 @@ 6102FE4B0E07803800F85D09 /* SUDiskImageUnarchiver.m in Sources */, 6102FE5C0E08C7EC00F85D09 /* SUUnarchiver_Private.m in Sources */, 61D85D6D0E10B2ED00F9B4A9 /* SUPipedUnarchiver.m in Sources */, + 61EF67560E25B58D00F754E0 /* SUHost.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1041,7 +1033,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(NATIVE_ARCH)"; + ARCHS = "$(NATIVE_ARCH_ACTUAL)"; COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; GCC_DYNAMIC_NO_PIC = NO;