upgrade_detector.cc revision c407dc5cd9bdc5668497f21b26b09d988ab439de
1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/upgrade_detector.h" 6 7#include <string> 8 9#include "base/command_line.h" 10#include "base/file_version_info.h" 11#include "base/scoped_ptr.h" 12#include "base/time.h" 13#include "base/task.h" 14#include "base/utf_string_conversions.h" 15#include "base/version.h" 16#include "chrome/browser/chrome_thread.h" 17#include "chrome/browser/pref_service.h" 18#include "chrome/common/chrome_switches.h" 19#include "chrome/common/chrome_version_info.h" 20#include "chrome/common/notification_service.h" 21#include "chrome/common/notification_type.h" 22#include "chrome/common/pref_names.h" 23#include "chrome/installer/util/browser_distribution.h" 24 25#if defined(OS_WIN) 26#include "chrome/installer/util/install_util.h" 27#elif defined(OS_MACOSX) 28#include "chrome/browser/cocoa/keystone_glue.h" 29#elif defined(OS_POSIX) 30#include "base/process_util.h" 31#include "chrome/installer/util/version.h" 32#endif 33 34// TODO(finnur): For the stable channel we want to check daily and notify 35// the user if more than 2 weeks have passed since the upgrade happened 36// (without a reboot). For the dev channel however, I want quicker feedback 37// on how the feature works so I'm checking every hour and notifying the 38// user immediately. 39 40namespace { 41 42// How often to check for an upgrade. 43const int kCheckForUpgradeEveryMs = 60 * 60 * 1000; // 1 hour. 44 45// How long to wait before notifying the user about the upgrade. 46const int kNotifyUserAfterMs = 0; 47 48// The thread to run the upgrade detection code on. We use FILE for Linux 49// because we don't want to block the UI thread while launching a background 50// process and reading its output; on the Mac, checking for an upgrade 51// requires reading a file. 52const ChromeThread::ID kDetectUpgradeTaskID = 53#if defined(OS_POSIX) 54 ChromeThread::FILE; 55#else 56 ChromeThread::UI; 57#endif 58 59// This task checks the currently running version of Chrome against the 60// installed version. If the installed version is newer, it runs the passed 61// callback task. Otherwise it just deletes the task. 62class DetectUpgradeTask : public Task { 63 public: 64 explicit DetectUpgradeTask(Task* upgrade_detected_task) 65 : upgrade_detected_task_(upgrade_detected_task) { 66 } 67 68 virtual ~DetectUpgradeTask() { 69 if (upgrade_detected_task_) { 70 // This has to get deleted on the same thread it was created. 71 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, 72 new DeleteTask<Task>(upgrade_detected_task_)); 73 } 74 } 75 76 virtual void Run() { 77 DCHECK(ChromeThread::CurrentlyOn(kDetectUpgradeTaskID)); 78 79 using installer::Version; 80 scoped_ptr<Version> installed_version; 81 82#if defined(OS_WIN) 83 // Get the version of the currently *installed* instance of Chrome, 84 // which might be newer than the *running* instance if we have been 85 // upgraded in the background. 86 installed_version.reset(InstallUtil::GetChromeVersion(false)); 87 if (!installed_version.get()) { 88 // User level Chrome is not installed, check system level. 89 installed_version.reset(InstallUtil::GetChromeVersion(true)); 90 } 91#elif defined(OS_MACOSX) 92 installed_version.reset( 93 Version::GetVersionFromString( 94 keystone_glue::CurrentlyInstalledVersion())); 95#elif defined(OS_POSIX) 96 // POSIX but not Mac OS X: Linux, etc. 97 CommandLine command_line(*CommandLine::ForCurrentProcess()); 98 command_line.AppendSwitch(switches::kProductVersion); 99 std::string reply; 100 if (!base::GetAppOutput(command_line, &reply)) { 101 DLOG(ERROR) << "Failed to get current file version"; 102 return; 103 } 104 105 installed_version.reset(Version::GetVersionFromString(ASCIIToUTF16(reply))); 106#endif 107 108 // Get the version of the currently *running* instance of Chrome. 109 scoped_ptr<FileVersionInfo> version(chrome::GetChromeVersionInfo()); 110 if (version.get() == NULL) { 111 NOTREACHED() << "Failed to get current file version"; 112 return; 113 } 114 115 scoped_ptr<Version> running_version( 116 Version::GetVersionFromString(WideToUTF16(version->file_version()))); 117 if (running_version.get() == NULL) { 118 NOTREACHED() << "Failed to parse version info"; 119 return; 120 } 121 122 // |installed_version| may be NULL when the user downgrades on Linux (by 123 // switching from dev to beta channel, for example). The user needs a 124 // restart in this case as well. See http://crbug.com/46547 125 if (!installed_version.get() || 126 installed_version->IsHigherThan(running_version.get())) { 127 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, 128 upgrade_detected_task_); 129 upgrade_detected_task_ = NULL; 130 } 131 } 132 133 private: 134 Task* upgrade_detected_task_; 135}; 136 137} // namespace 138 139// static 140void UpgradeDetector::RegisterPrefs(PrefService* prefs) { 141 prefs->RegisterBooleanPref(prefs::kRestartLastSessionOnShutdown, false); 142} 143 144UpgradeDetector::UpgradeDetector() 145 : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), 146 notify_upgrade_(false) { 147 // Windows: only enable upgrade notifications for official builds. 148 // Mac: only enable them if the updater (Keystone) is present. 149 // Linux (and other POSIX): always enable regardless of branding. 150#if (defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)) || defined(OS_POSIX) 151#if defined(OS_MACOSX) 152 if (keystone_glue::KeystoneEnabled()) 153#endif 154 { 155 detect_upgrade_timer_.Start( 156 base::TimeDelta::FromMilliseconds(kCheckForUpgradeEveryMs), 157 this, &UpgradeDetector::CheckForUpgrade); 158 } 159#endif 160} 161 162UpgradeDetector::~UpgradeDetector() { 163} 164 165void UpgradeDetector::CheckForUpgrade() { 166 method_factory_.RevokeAll(); 167 Task* callback_task = 168 method_factory_.NewRunnableMethod(&UpgradeDetector::UpgradeDetected); 169 ChromeThread::PostTask(kDetectUpgradeTaskID, FROM_HERE, 170 new DetectUpgradeTask(callback_task)); 171} 172 173void UpgradeDetector::UpgradeDetected() { 174 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); 175 176 // Stop the recurring timer (that is checking for changes). 177 detect_upgrade_timer_.Stop(); 178 179 NotificationService::current()->Notify( 180 NotificationType::UPGRADE_DETECTED, 181 Source<UpgradeDetector>(this), 182 NotificationService::NoDetails()); 183 184 // Start the OneShot timer for notifying the user after a certain period. 185 upgrade_notification_timer_.Start( 186 base::TimeDelta::FromMilliseconds(kNotifyUserAfterMs), 187 this, &UpgradeDetector::NotifyOnUpgrade); 188} 189 190void UpgradeDetector::NotifyOnUpgrade() { 191 notify_upgrade_ = true; 192 193 NotificationService::current()->Notify( 194 NotificationType::UPGRADE_RECOMMENDED, 195 Source<UpgradeDetector>(this), 196 NotificationService::NoDetails()); 197} 198