| Index: chrome/app/keystone_glue.mm
|
| ===================================================================
|
| --- chrome/app/keystone_glue.mm (revision 30049)
|
| +++ chrome/app/keystone_glue.mm (working copy)
|
| @@ -2,17 +2,13 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| -#include "base/mac_util.h"
|
| #import "chrome/app/keystone_glue.h"
|
|
|
| -@interface KeystoneGlue(Private)
|
| +#include "base/logging.h"
|
| +#include "base/mac_util.h"
|
| +#import "base/worker_pool_mac.h"
|
| +#include "chrome/common/chrome_constants.h"
|
|
|
| -// Called periodically to announce activity by pinging the Keystone server.
|
| -- (void)markActive:(NSTimer*)timer;
|
| -
|
| -@end
|
| -
|
| -
|
| // Provide declarations of the Keystone registration bits needed here. From
|
| // KSRegistration.h.
|
| typedef enum { kKSPathExistenceChecker } KSExistenceCheckerType;
|
| @@ -31,7 +27,9 @@
|
| #define KSRegistrationPreserveExistingTag nil
|
|
|
| @interface KSRegistration : NSObject
|
| +
|
| + (id)registrationWithProductID:(NSString*)productID;
|
| +
|
| // Older API
|
| - (BOOL)registerWithVersion:(NSString*)version
|
| existenceCheckerType:(KSExistenceCheckerType)xctype
|
| @@ -43,20 +41,65 @@
|
| existenceCheckerString:(NSString*)xc
|
| serverURLString:(NSString*)serverURLString
|
| preserveTTToken:(BOOL)preserveToken
|
| - tag:(NSString *)tag;
|
| + tag:(NSString*)tag;
|
| +
|
| - (void)setActive;
|
| - (void)checkForUpdate;
|
| - (void)startUpdate;
|
| -@end
|
|
|
| +@end // @interface KSRegistration
|
|
|
| +@interface KeystoneGlue(Private)
|
| +
|
| +// Called periodically to announce activity by pinging the Keystone server.
|
| +- (void)markActive:(NSTimer*)timer;
|
| +
|
| +// Called when an update check or update installation is complete. Posts the
|
| +// kAutoupdateStatusNotification notification to the default notification
|
| +// center.
|
| +- (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version;
|
| +
|
| +// These three methods are used to determine the version of the application
|
| +// currently installed on disk, compare that to the currently-running version,
|
| +// decide whether any updates have been installed, and call
|
| +// -updateStatus:version:.
|
| +//
|
| +// In order to check the version on disk, the installed application's
|
| +// Info.plist dictionary must be read; in order to see changes as updates are
|
| +// applied, the dictionary must be read each time, bypassing any caches such
|
| +// as the one that NSBundle might be maintaining. Reading files can be a
|
| +// blocking operation, and blocking operations are to be avoided on the main
|
| +// thread. I'm not quite sure what jank means, but I bet that a blocked main
|
| +// thread would cause some of it.
|
| +//
|
| +// -determineUpdateStatusAsync is called on the main thread to initiate the
|
| +// operation. It performs initial set-up work that must be done on the main
|
| +// thread and arranges for -determineUpdateStatusAtPath: to be called on a
|
| +// work queue thread managed by NSOperationQueue.
|
| +// -determineUpdateStatusAtPath: then reads the Info.plist, gets the version
|
| +// from the CFBundleShortVersionString key, and performs
|
| +// -determineUpdateStatusForVersion: on the main thread.
|
| +// -determineUpdateStatusForVersion: does the actual comparison of the version
|
| +// on disk with the running version and calls -updateStatus:version: with the
|
| +// results of its analysis.
|
| +- (void)determineUpdateStatusAsync;
|
| +- (void)determineUpdateStatusAtPath:(NSString*)appPath;
|
| +- (void)determineUpdateStatusForVersion:(NSString*)version;
|
| +
|
| +@end // @interface KeystoneGlue(Private)
|
| +
|
| +const NSString* const kAutoupdateStatusNotification =
|
| + @"AutoupdateStatusNotification";
|
| +const NSString* const kAutoupdateStatusStatus = @"status";
|
| +const NSString* const kAutoupdateStatusVersion = @"version";
|
| +
|
| @implementation KeystoneGlue
|
|
|
| +// TODO(jrg): use base::SingletonObjC<KeystoneGlue>
|
| +static KeystoneGlue* sDefaultKeystoneGlue = nil; // leaked
|
| +
|
| + (id)defaultKeystoneGlue {
|
| - // TODO(jrg): use base::SingletonObjC<KeystoneGlue>
|
| - static KeystoneGlue* sDefaultKeystoneGlue = nil; // leaked
|
| -
|
| - if (sDefaultKeystoneGlue == nil) {
|
| + if (!sDefaultKeystoneGlue) {
|
| sDefaultKeystoneGlue = [[KeystoneGlue alloc] init];
|
| [sDefaultKeystoneGlue loadParameters];
|
| if (![sDefaultKeystoneGlue loadKeystoneRegistration]) {
|
| @@ -67,6 +110,29 @@
|
| return sDefaultKeystoneGlue;
|
| }
|
|
|
| ++ (void)releaseDefaultKeystoneGlue {
|
| + [sDefaultKeystoneGlue release];
|
| + sDefaultKeystoneGlue = nil;
|
| +}
|
| +
|
| +- (id)init {
|
| + if ((self = [super init])) {
|
| + NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
|
| +
|
| + [center addObserver:self
|
| + selector:@selector(checkForUpdateComplete:)
|
| + name:KSRegistrationCheckForUpdateNotification
|
| + object:nil];
|
| +
|
| + [center addObserver:self
|
| + selector:@selector(installUpdateComplete:)
|
| + name:KSRegistrationStartUpdateNotification
|
| + object:nil];
|
| + }
|
| +
|
| + return self;
|
| +}
|
| +
|
| - (void)dealloc {
|
| [url_ release];
|
| [productID_ release];
|
| @@ -174,64 +240,155 @@
|
| [ksr setActive];
|
| }
|
|
|
| -- (void)checkComplete:(NSNotification *)notification {
|
| - NSDictionary *userInfo = [notification userInfo];
|
| - BOOL updatesAvailable = [[userInfo objectForKey:KSRegistrationStatusKey]
|
| - boolValue];
|
| - NSString *latestVersion = [userInfo objectForKey:KSRegistrationVersionKey];
|
| +- (void)checkForUpdate {
|
| + if (!registration_) {
|
| + [self updateStatus:kAutoupdateCheckFailed version:nil];
|
| + return;
|
| + }
|
|
|
| - [checkTarget_ upToDateCheckCompleted:updatesAvailable
|
| - latestVersion:latestVersion];
|
| - [checkTarget_ release];
|
| - checkTarget_ = nil;
|
| + [registration_ checkForUpdate];
|
|
|
| - NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
|
| - [center removeObserver:self
|
| - name:KSRegistrationCheckForUpdateNotification
|
| - object:nil];
|
| + // Upon completion, KSRegistrationCheckForUpdateNotification will be posted,
|
| + // and -checkForUpdateComplete: will be called.
|
| }
|
|
|
| -- (BOOL)checkForUpdate:(NSObject<KeystoneGlueCallbacks>*)target {
|
| - if (registration_ == nil)
|
| - return NO;
|
| - NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
|
| - [center addObserver:self
|
| - selector:@selector(checkComplete:)
|
| - name:KSRegistrationCheckForUpdateNotification
|
| - object:nil];
|
| - checkTarget_ = [target retain];
|
| - [registration_ checkForUpdate];
|
| - return YES;
|
| +- (void)checkForUpdateComplete:(NSNotification*)notification {
|
| + NSDictionary* userInfo = [notification userInfo];
|
| + BOOL updatesAvailable =
|
| + [[userInfo objectForKey:KSRegistrationStatusKey] boolValue];
|
| +
|
| + if (updatesAvailable) {
|
| + // If an update is known to be available, go straight to
|
| + // -updateStatus:version:. It doesn't matter what's currently on disk.
|
| + NSString* version = [userInfo objectForKey:KSRegistrationVersionKey];
|
| + [self updateStatus:kAutoupdateAvailable version:version];
|
| + } else {
|
| + // If no updates are available, check what's on disk, because an update
|
| + // may have already been installed. This check happens on another thread,
|
| + // and -updateStatus:version: will be called on the main thread when done.
|
| + [self determineUpdateStatusAsync];
|
| + }
|
| }
|
|
|
| -- (void)startUpdateComplete:(NSNotification *)notification {
|
| - NSDictionary *userInfo = [notification userInfo];
|
| - BOOL checkSuccessful = [[userInfo objectForKey:KSUpdateCheckSuccessfulKey]
|
| - boolValue];
|
| - int installs = [[userInfo objectForKey:KSUpdateCheckSuccessfullyInstalledKey]
|
| - intValue];
|
| +- (void)installUpdate {
|
| + if (!registration_) {
|
| + [self updateStatus:kAutoupdateInstallFailed version:nil];
|
| + return;
|
| + }
|
|
|
| - [startTarget_ updateCompleted:checkSuccessful installs:installs];
|
| - [startTarget_ release];
|
| - startTarget_ = nil;
|
| + [registration_ startUpdate];
|
|
|
| - NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
|
| - [center removeObserver:self
|
| - name:KSRegistrationStartUpdateNotification
|
| - object:nil];
|
| + // Upon completion, KSRegistrationStartUpdateNotification will be posted,
|
| + // and -installUpdateComplete: will be called.
|
| }
|
|
|
| -- (BOOL)startUpdate:(NSObject<KeystoneGlueCallbacks>*)target {
|
| - if (registration_ == nil)
|
| - return NO;
|
| - NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
|
| - [center addObserver:self
|
| - selector:@selector(startUpdateComplete:)
|
| - name:KSRegistrationStartUpdateNotification
|
| - object:nil];
|
| - startTarget_ = [target retain];
|
| - [registration_ startUpdate];
|
| - return YES;
|
| +- (void)installUpdateComplete:(NSNotification*)notification {
|
| + NSDictionary* userInfo = [notification userInfo];
|
| + BOOL checkSuccessful =
|
| + [[userInfo objectForKey:KSUpdateCheckSuccessfulKey] boolValue];
|
| + int installs =
|
| + [[userInfo objectForKey:KSUpdateCheckSuccessfullyInstalledKey] intValue];
|
| +
|
| + if (!checkSuccessful || !installs) {
|
| + [self updateStatus:kAutoupdateInstallFailed version:nil];
|
| + } else {
|
| + updateSuccessfullyInstalled_ = YES;
|
| +
|
| + // Nothing in the notification dictionary reports the version that was
|
| + // installed. Figure it out based on what's on disk.
|
| + [self determineUpdateStatusAsync];
|
| + }
|
| }
|
|
|
| -@end
|
| +// Runs on the main thread.
|
| +- (void)determineUpdateStatusAsync {
|
| + // NSBundle is not documented as being thread-safe. Do NSBundle operations
|
| + // on the main thread before jumping over to a NSOperationQueue-managed
|
| + // thread to do blocking file input.
|
| + DCHECK([NSThread isMainThread]);
|
| +
|
| + SEL selector = @selector(determineUpdateStatusAtPath:);
|
| + NSString* appPath = [[NSBundle mainBundle] bundlePath];
|
| + NSInvocationOperation* operation =
|
| + [[[NSInvocationOperation alloc] initWithTarget:self
|
| + selector:selector
|
| + object:appPath] autorelease];
|
| +
|
| + NSOperationQueue* operationQueue = [WorkerPoolObjC sharedOperationQueue];
|
| + [operationQueue addOperation:operation];
|
| +}
|
| +
|
| +// Runs on a thread managed by NSOperationQueue.
|
| +- (void)determineUpdateStatusAtPath:(NSString*)appPath {
|
| + DCHECK(![NSThread isMainThread]);
|
| +
|
| + NSString* appInfoPlistPath =
|
| + [[appPath stringByAppendingPathComponent:@"Contents"]
|
| + stringByAppendingPathComponent:@"Info.plist"];
|
| + NSDictionary* infoPlist =
|
| + [NSDictionary dictionaryWithContentsOfFile:appInfoPlistPath];
|
| + NSString* version = [infoPlist objectForKey:@"CFBundleShortVersionString"];
|
| +
|
| + [self performSelectorOnMainThread:@selector(determineUpdateStatusForVersion:)
|
| + withObject:version
|
| + waitUntilDone:NO];
|
| +}
|
| +
|
| +// Runs on the main thread.
|
| +- (void)determineUpdateStatusForVersion:(NSString*)version {
|
| + DCHECK([NSThread isMainThread]);
|
| +
|
| + AutoupdateStatus status;
|
| + if (updateSuccessfullyInstalled_) {
|
| + // If an update was successfully installed and this object saw it happen,
|
| + // then don't even bother comparing versions.
|
| + status = kAutoupdateInstalled;
|
| + } else {
|
| + NSString* currentVersion =
|
| + [NSString stringWithUTF8String:chrome::kChromeVersion];
|
| + if (!version) {
|
| + // If the version on disk could not be determined, assume that
|
| + // whatever's running is current.
|
| + version = currentVersion;
|
| + status = kAutoupdateCurrent;
|
| + } else if ([version isEqualToString:currentVersion]) {
|
| + status = kAutoupdateCurrent;
|
| + } else {
|
| + // If the version on disk doesn't match what's currently running, an
|
| + // update must have been applied in the background, without this app's
|
| + // direct participation. Leave updateSuccessfullyInstalled_ alone
|
| + // because there's no direct knowledge of what actually happened.
|
| + status = kAutoupdateInstalled;
|
| + }
|
| + }
|
| +
|
| + [self updateStatus:status version:version];
|
| +}
|
| +
|
| +- (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version {
|
| + NSNumber* statusNumber = [NSNumber numberWithInt:status];
|
| + NSMutableDictionary* dictionary =
|
| + [NSMutableDictionary dictionaryWithObject:statusNumber
|
| + forKey:kAutoupdateStatusStatus];
|
| + if (version) {
|
| + [dictionary setObject:version forKey:kAutoupdateStatusVersion];
|
| + }
|
| +
|
| + NSNotification* notification =
|
| + [NSNotification notificationWithName:kAutoupdateStatusNotification
|
| + object:self
|
| + userInfo:dictionary];
|
| + recentNotification_.reset([notification retain]);
|
| +
|
| + [[NSNotificationCenter defaultCenter] postNotification:notification];
|
| +}
|
| +
|
| +- (NSNotification*)recentNotification {
|
| + return [[recentNotification_ retain] autorelease];
|
| +}
|
| +
|
| +- (void)clearRecentNotification {
|
| + recentNotification_.reset(nil);
|
| +}
|
| +
|
| +@end // @implementation KeystoneGlue
|
|
|