blob: 331fe577c1fc3d82c235fdab719d107f36c9be0e [file] [log] [blame]
// Copyright (c) 2012 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.
#include "chrome/browser/ui/webui/help/version_updater_mac.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#import "chrome/browser/mac/keystone_glue.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
// KeystoneObserver is a simple notification observer for Keystone status
// updates. It will be created and managed by VersionUpdaterMac.
@interface KeystoneObserver : NSObject {
@private
VersionUpdaterMac* versionUpdater_; // Weak.
}
// Initialize an observer with an updater. The updater owns this object.
- (id)initWithUpdater:(VersionUpdaterMac*)updater;
// Notification callback, called with the status of keystone operations.
- (void)handleStatusNotification:(NSNotification*)notification;
@end // @interface KeystoneObserver
@implementation KeystoneObserver
- (id)initWithUpdater:(VersionUpdaterMac*)updater {
if ((self = [super init])) {
versionUpdater_ = updater;
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(handleStatusNotification:)
name:kAutoupdateStatusNotification
object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)handleStatusNotification:(NSNotification*)notification {
versionUpdater_->UpdateStatus([notification userInfo]);
}
@end // @implementation KeystoneObserver
VersionUpdater* VersionUpdater::Create() {
return new VersionUpdaterMac;
}
VersionUpdaterMac::VersionUpdaterMac() {
keystone_observer_.reset([[KeystoneObserver alloc] initWithUpdater:this]);
}
VersionUpdaterMac::~VersionUpdaterMac() {
}
void VersionUpdaterMac::CheckForUpdate(
const StatusCallback& status_callback,
const PromoteCallback& promote_callback) {
// Copy the callbacks, we will re-use this for the remaining lifetime
// of this object.
status_callback_ = status_callback;
promote_callback_ = promote_callback;
KeystoneGlue* keystone_glue = [KeystoneGlue defaultKeystoneGlue];
if (keystone_glue && ![keystone_glue isOnReadOnlyFilesystem]) {
AutoupdateStatus recent_status = [keystone_glue recentStatus];
if ([keystone_glue asyncOperationPending] ||
recent_status == kAutoupdateRegisterFailed ||
recent_status == kAutoupdateNeedsPromotion) {
// If an asynchronous update operation is currently pending, such as a
// check for updates or an update installation attempt, set the status
// up correspondingly without launching a new update check.
//
// If registration failed, no other operations make sense, so just go
// straight to the error.
UpdateStatus([[keystone_glue recentNotification] userInfo]);
} else {
// Launch a new update check, even if one was already completed, because
// a new update may be available or a new update may have been installed
// in the background since the last time the Help page was displayed.
[keystone_glue checkForUpdate];
// Immediately, kAutoupdateStatusNotification will be posted, with status
// kAutoupdateChecking.
//
// Upon completion, kAutoupdateStatusNotification will be posted with a
// status indicating the result of the check.
}
UpdateShowPromoteButton();
} else {
// There is no glue, or the application is on a read-only filesystem.
// Updates and promotions are impossible.
status_callback_.Run(DISABLED, 0, string16());
}
}
void VersionUpdaterMac::PromoteUpdater() const {
// Tell Keystone to make software updates available for all users.
[[KeystoneGlue defaultKeystoneGlue] promoteTicket];
// Immediately, kAutoupdateStatusNotification will be posted, and
// UpdateStatus() will be called with status kAutoupdatePromoting.
//
// Upon completion, kAutoupdateStatusNotification will be posted, and
// UpdateStatus() will be called with a status indicating a result of the
// installation attempt.
//
// If the promotion was successful, KeystoneGlue will re-register the ticket
// and UpdateStatus() will be called again indicating first that
// registration is in progress and subsequently that it has completed.
}
void VersionUpdaterMac::RelaunchBrowser() const {
// Tell the Broweser to restart if possible.
chrome::AttemptRestart();
}
void VersionUpdaterMac::UpdateStatus(NSDictionary* dictionary) {
AutoupdateStatus keystone_status = static_cast<AutoupdateStatus>(
[[dictionary objectForKey:kAutoupdateStatusStatus] intValue]);
bool enable_promote_button = true;
string16 message;
Status status;
switch (keystone_status) {
case kAutoupdateRegistering:
case kAutoupdateChecking:
status = CHECKING;
enable_promote_button = false;
break;
case kAutoupdateRegistered:
case kAutoupdatePromoted:
UpdateShowPromoteButton();
// Go straight into an update check. Return immediately, this routine
// will be re-entered shortly with kAutoupdateChecking.
[[KeystoneGlue defaultKeystoneGlue] checkForUpdate];
return;
case kAutoupdateCurrent:
status = UPDATED;
break;
case kAutoupdateAvailable:
// Install the update automatically. Return immediately, this routine
// will be re-entered shortly with kAutoupdateInstalling.
[[KeystoneGlue defaultKeystoneGlue] installUpdate];
return;
case kAutoupdateInstalling:
status = UPDATING;
enable_promote_button = false;
break;
case kAutoupdateInstalled:
status = NEARLY_UPDATED;
break;
case kAutoupdatePromoting:
#if 1
// TODO(mark): KSRegistration currently handles the promotion
// synchronously, meaning that the main thread's loop doesn't spin,
// meaning that animations and other updates to the window won't occur
// until KSRegistration is done with promotion. This looks laggy and bad
// and probably qualifies as "jank." For now, there just won't be any
// visual feedback while promotion is in progress, but it should complete
// (or fail) very quickly. http://b/2290009.
return;
#endif
status = CHECKING;
enable_promote_button = false;
break;
case kAutoupdateRegisterFailed:
enable_promote_button = false;
// Fall through.
case kAutoupdateCheckFailed:
case kAutoupdateInstallFailed:
case kAutoupdatePromoteFailed:
status = FAILED;
message = l10n_util::GetStringFUTF16Int(IDS_UPGRADE_ERROR,
keystone_status);
break;
case kAutoupdateNeedsPromotion:
{
status = FAILED;
string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
message = l10n_util:: GetStringFUTF16(IDS_PROMOTE_INFOBAR_TEXT,
product_name);
}
break;
default:
NOTREACHED();
return;
}
if (!status_callback_.is_null())
status_callback_.Run(status, 0, message);
if (!promote_callback_.is_null()) {
PromotionState promotion_state = PROMOTE_HIDDEN;
if (show_promote_button_)
promotion_state = enable_promote_button ? PROMOTE_ENABLED
: PROMOTE_DISABLED;
promote_callback_.Run(promotion_state);
}
}
void VersionUpdaterMac::UpdateShowPromoteButton() {
KeystoneGlue* keystone_glue = [KeystoneGlue defaultKeystoneGlue];
AutoupdateStatus recent_status = [keystone_glue recentStatus];
if (recent_status == kAutoupdateRegistering ||
recent_status == kAutoupdateRegisterFailed ||
recent_status == kAutoupdatePromoted) {
// Promotion isn't possible at this point.
show_promote_button_ = false;
} else if (recent_status == kAutoupdatePromoting ||
recent_status == kAutoupdatePromoteFailed) {
// Show promotion UI because the user either just clicked that button or
// because the user should be able to click it again.
show_promote_button_ = true;
} else {
// Show the promote button if promotion is a possibility.
show_promote_button_ = [keystone_glue wantsPromotion];
}
}