| Index: chrome/app/keystone_glue.mm
|
| ===================================================================
|
| --- chrome/app/keystone_glue.mm (revision 33240)
|
| +++ chrome/app/keystone_glue.mm (working copy)
|
| @@ -1,389 +0,0 @@
|
| -// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| -
|
| -#import "chrome/app/keystone_glue.h"
|
| -
|
| -#include "base/logging.h"
|
| -#include "base/mac_util.h"
|
| -#import "base/worker_pool_mac.h"
|
| -#include "chrome/common/chrome_constants.h"
|
| -
|
| -namespace {
|
| -
|
| -// Provide declarations of the Keystone registration bits needed here. From
|
| -// KSRegistration.h.
|
| -typedef enum { kKSPathExistenceChecker } KSExistenceCheckerType;
|
| -
|
| -NSString *KSRegistrationCheckForUpdateNotification =
|
| - @"KSRegistrationCheckForUpdateNotification";
|
| -NSString *KSRegistrationStatusKey = @"Status";
|
| -NSString *KSRegistrationVersionKey = @"Version";
|
| -NSString *KSRegistrationUpdateCheckErrorKey = @"Error";
|
| -
|
| -NSString *KSRegistrationStartUpdateNotification =
|
| - @"KSRegistrationStartUpdateNotification";
|
| -NSString *KSUpdateCheckSuccessfulKey = @"CheckSuccessful";
|
| -NSString *KSUpdateCheckSuccessfullyInstalledKey = @"SuccessfullyInstalled";
|
| -
|
| -NSString *KSRegistrationRemoveExistingTag = @"";
|
| -#define KSRegistrationPreserveExistingTag nil
|
| -
|
| -} // namespace
|
| -
|
| -@interface KSRegistration : NSObject
|
| -
|
| -+ (id)registrationWithProductID:(NSString*)productID;
|
| -
|
| -- (BOOL)registerWithVersion:(NSString*)version
|
| - existenceCheckerType:(KSExistenceCheckerType)xctype
|
| - existenceCheckerString:(NSString*)xc
|
| - serverURLString:(NSString*)serverURLString
|
| - preserveTTToken:(BOOL)preserveToken
|
| - tag:(NSString*)tag;
|
| -
|
| -- (void)setActive;
|
| -- (void)checkForUpdate;
|
| -- (void)startUpdate;
|
| -
|
| -@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 {
|
| - if (!sDefaultKeystoneGlue) {
|
| - sDefaultKeystoneGlue = [[KeystoneGlue alloc] init];
|
| - [sDefaultKeystoneGlue loadParameters];
|
| - if (![sDefaultKeystoneGlue loadKeystoneRegistration]) {
|
| - [sDefaultKeystoneGlue release];
|
| - sDefaultKeystoneGlue = nil;
|
| - }
|
| - }
|
| - return sDefaultKeystoneGlue;
|
| -}
|
| -
|
| -- (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];
|
| - [version_ release];
|
| - [channel_ release];
|
| - [registration_ release];
|
| - [[NSNotificationCenter defaultCenter] removeObserver:self];
|
| - [super dealloc];
|
| -}
|
| -
|
| -- (NSDictionary*)infoDictionary {
|
| - // Use mac_util::MainAppBundle() to get the app framework's dictionary.
|
| - return [mac_util::MainAppBundle() infoDictionary];
|
| -}
|
| -
|
| -- (void)loadParameters {
|
| - NSDictionary* infoDictionary = [self infoDictionary];
|
| - NSString* url = [infoDictionary objectForKey:@"KSUpdateURL"];
|
| - NSString* product = [infoDictionary objectForKey:@"KSProductID"];
|
| - if (product == nil) {
|
| - // Use [NSBundle mainBundle] to fall back to the app's own bundle
|
| - // identifier, not the app framework's.
|
| - product = [[NSBundle mainBundle] bundleIdentifier];
|
| - }
|
| - NSString* version = [infoDictionary objectForKey:@"KSVersion"];
|
| - if (!product || !url || !version) {
|
| - // If parameters required for Keystone are missing, don't use it.
|
| - return;
|
| - }
|
| -
|
| - NSString* channel = [infoDictionary objectForKey:@"KSChannelID"];
|
| - // The stable channel has no tag. If updating to stable, remove the
|
| - // dev and beta tags since we've been "promoted".
|
| - if (channel == nil)
|
| - channel = KSRegistrationRemoveExistingTag;
|
| -
|
| - url_ = [url retain];
|
| - productID_ = [product retain];
|
| - version_ = [version retain];
|
| - channel_ = [channel retain];
|
| -}
|
| -
|
| -- (BOOL)loadKeystoneRegistration {
|
| - if (!productID_ || !url_ || !version_)
|
| - return NO;
|
| -
|
| - // Load the KeystoneRegistration framework bundle if present. It lives
|
| - // inside the framework, so use mac_util::MainAppBundle();
|
| - NSString* ksrPath =
|
| - [[mac_util::MainAppBundle() privateFrameworksPath]
|
| - stringByAppendingPathComponent:@"KeystoneRegistration.framework"];
|
| - NSBundle* ksrBundle = [NSBundle bundleWithPath:ksrPath];
|
| - [ksrBundle load];
|
| -
|
| - // Harness the KSRegistration class.
|
| - Class ksrClass = [ksrBundle classNamed:@"KSRegistration"];
|
| - KSRegistration* ksr = [ksrClass registrationWithProductID:productID_];
|
| - if (!ksr)
|
| - return NO;
|
| -
|
| - registration_ = [ksr retain];
|
| - return YES;
|
| -}
|
| -
|
| -- (void)registerWithKeystone {
|
| - // The existence checks should use the path to the app bundle, not the
|
| - // app framework bundle, so use [NSBundle mainBundle] instead of
|
| - // mac_util::MainBundle().
|
| - [registration_ registerWithVersion:version_
|
| - existenceCheckerType:kKSPathExistenceChecker
|
| - existenceCheckerString:[[NSBundle mainBundle] bundlePath]
|
| - serverURLString:url_
|
| - preserveTTToken:YES
|
| - tag:channel_];
|
| -
|
| - // Mark an active RIGHT NOW; don't wait an hour for the first one.
|
| - [registration_ setActive];
|
| -
|
| - // Set up hourly activity pings.
|
| - timer_ = [NSTimer scheduledTimerWithTimeInterval:60 * 60 // One hour
|
| - target:self
|
| - selector:@selector(markActive:)
|
| - userInfo:registration_
|
| - repeats:YES];
|
| -}
|
| -
|
| -- (void)stopTimer {
|
| - [timer_ invalidate];
|
| -}
|
| -
|
| -- (void)markActive:(NSTimer*)timer {
|
| - KSRegistration* ksr = [timer userInfo];
|
| - [ksr setActive];
|
| -}
|
| -
|
| -- (void)checkForUpdate {
|
| - DCHECK(![self asyncOperationPending]);
|
| -
|
| - if (!registration_) {
|
| - [self updateStatus:kAutoupdateCheckFailed version:nil];
|
| - return;
|
| - }
|
| -
|
| - [self updateStatus:kAutoupdateChecking version:nil];
|
| -
|
| - [registration_ checkForUpdate];
|
| -
|
| - // Upon completion, KSRegistrationCheckForUpdateNotification will be posted,
|
| - // and -checkForUpdateComplete: will be called.
|
| -}
|
| -
|
| -- (void)checkForUpdateComplete:(NSNotification*)notification {
|
| - NSDictionary* userInfo = [notification userInfo];
|
| -
|
| - if ([[userInfo objectForKey:KSRegistrationUpdateCheckErrorKey] boolValue]) {
|
| - [self updateStatus:kAutoupdateCheckFailed version:nil];
|
| - } else if ([[userInfo objectForKey:KSRegistrationStatusKey] boolValue]) {
|
| - // 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)installUpdate {
|
| - DCHECK(![self asyncOperationPending]);
|
| -
|
| - if (!registration_) {
|
| - [self updateStatus:kAutoupdateInstallFailed version:nil];
|
| - return;
|
| - }
|
| -
|
| - [self updateStatus:kAutoupdateInstalling version:nil];
|
| -
|
| - [registration_ startUpdate];
|
| -
|
| - // Upon completion, KSRegistrationStartUpdateNotification will be posted,
|
| - // and -installUpdateComplete: will be called.
|
| -}
|
| -
|
| -- (void)installUpdateComplete:(NSNotification*)notification {
|
| - NSDictionary* userInfo = [notification userInfo];
|
| -
|
| - if (![[userInfo objectForKey:KSUpdateCheckSuccessfulKey] boolValue] ||
|
| - ![[userInfo objectForKey:KSUpdateCheckSuccessfullyInstalledKey]
|
| - intValue]) {
|
| - [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];
|
| - }
|
| -}
|
| -
|
| -// 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];
|
| -}
|
| -
|
| -- (AutoupdateStatus)recentStatus {
|
| - NSDictionary* dictionary = [recentNotification_ userInfo];
|
| - return static_cast<AutoupdateStatus>(
|
| - [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]);
|
| -}
|
| -
|
| -- (BOOL)asyncOperationPending {
|
| - AutoupdateStatus status = [self recentStatus];
|
| - return status == kAutoupdateChecking || status == kAutoupdateInstalling;
|
| -}
|
| -
|
| -@end // @implementation KeystoneGlue
|
|
|